线程的同步
同步代码块实现:继承Thread线程安全问题
① 操作共享数据的代码,即为需要被同步的代码。(不能包含代码多了,也不能包含代码少了)
② 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
③ 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
class RWindow2 extends Thread {
public static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
//正确的
// synchronized (obj){
synchronized (RWindow2.class) {//Class clazz = Window2.class,Window2.class只会加载一次
// synchronized (this) {//错误的方式:this代表着t1,t2,t3三个对象
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
RWindow2 rWindow1 = new RWindow2();
RWindow2 rWindow2 = new RWindow2();
RWindow2 rWindow3 = new RWindow2();
rWindow1.setName("窗口1");
rWindow2.setName("窗口2");
rWindow3.setName("窗口3");
rWindow1.start();
rWindow2.start();
rWindow3.start();
}
}
同步代码块实现:实现Runnable线程安全问题
class RWindow implements Runnable {
private int ticket = 100;
// Object obj = new Object();
@Override
public void run() {
while(true) {
// synchronized(obj) {//方法一
synchronized(this) { //方式二:此时的this:唯一的RWindow的对象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
RWindow rWindow = new RWindow();
Thread thread1 = new Thread(rWindow);
thread1.setName("窗口一");
thread1.start();
Thread thread2 = new Thread(rWindow);
thread2.setName("窗口二");
thread2.start();
Thread thread3 = new Thread(rWindow);
thread3.setName("窗口三");
thread3.start();
}
}
同步方法实现:接口Runnable线程安全问题
① 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
② 非静态的同步方法,同步监视器是:this。
③ 静态的同步方法,同步监视器是:当前类本身。
class RWindow2 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
public synchronized void show() {//同步监视器:this
// synchronized(this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
ticket--;
}
// }
}
}
public class WindowTest2 {
public static void main(String[] args) {
RWindow2 rWindow = new RWindow2();
Thread thread1 = new Thread(rWindow);
thread1.setName("窗口一");
thread1.start();
Thread thread2 = new Thread(rWindow);
thread2.setName("窗口二");
thread2.start();
Thread thread3 = new Thread(rWindow);
thread3.setName("窗口三");
thread3.start();
}
}
同步方法实现:继承Thread线程安全问题
class RWindow3 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show() {//同步监听器:Window4.class
// private synchronized void show() {//同步监听器:rWindow1,rWindow2,rWindow3 此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
ticket--;
}
}
public class WindowTest3 {
public static void main(String[] args) {
RWindow3 rWindow1 = new RWindow3();
RWindow3 rWindow2 = new RWindow3();
RWindow3 rWindow3 = new RWindow3();
rWindow1.setName("窗口一");
rWindow1.start();
rWindow2.setName("窗口二");
rWindow2.start();
rWindow3.setName("窗口三");
rWindow3.start();
}
}
线程的死锁问题
死锁:
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续。
解决方法:
- 专门的算法、原则。
- 尽量减少同步资源的定义。
- 尽量避免嵌套同步。
/** 演示死锁问题 */
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append("c");
s2.append("3");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
Lock(锁)解决线程安全问题
解决线程安全问题方式三:Lock(锁)
---JDK5.0新增
synchronized
与lock
的异同
相同点:二者都可以解决线程安全问题。
不相同:synchronized
机制在执行完相应的同步代码以后,自动的释放同步监视器,Lock
需要手动的启动同步(lock()
),同时结束同步也需要手动实现(unlock()
)。
建议(优先使用顺序):Lock
同步代码块(已经进入了方法体,分配了相应资源)同步方法 (在方法体之外)。
class Window implements Runnable {
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
//2.调用clock()
lock.lock();
//2.调用Lock()
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为" + ticket);
ticket--;
} else {
break;
}
} finally {
//3.调用解锁方法:unLock
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
线程通信
涉及到的三个方法:
wait()
:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。notify()
:一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。notifyAll()
:一旦执行此方法,就会唤醒所有被wait的线程。
1)wait()
、notify()
、notifyAll()
三个方法必须使用在同步代码块或同步方法中。
2)wait()
、notify()
、notifyAll()
三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException
异常。
3)wait()
、notify()
、notifyAll()
三个方法是定义在java.lang.Object
类中。
sleep() 和 wait()的异同?
相同点:
1) 一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
1)两个方法声明的位置不同:Thread
类中声明sleep()
, Object
类中声明wait()
。
2)调用的要求不同:sleep()
可以在任何需要的场景下调用。 wait()
必须使用在同步代码块或同步方法中。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()
不会释放锁,wait()
会释放锁。
class Number {
private int number = 1;
private Object obj = new Object();
public Number(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
// 累加数字
public void printer(int number){
synchronized (obj) {
obj.notify();
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "添加成功。当前数字:" + this.number);
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.number += number;
try {
//使得调用如下wait() 方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Preson implements Runnable {
private Number number;
public Preson(Number number) {
this.number = number;
}
@Override
public void run() {
while (true) {
if (this.number.getNumber() <= 100) {
number.printer(1);
}else {
break;
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number(1);
Preson preson = new Preson(number);
Thread thread = new Thread(preson);
thread.setName("甲");
thread.start();
Thread thread2 = new Thread(preson);
thread2.setName("乙");
thread2.start();
}
}
JDK5.0新增线程创建方式
新增方式一:实现Callable接口
1)与使用Runnable
相比, Callable
功能更强大些。
2)相比run()
方法,可以有返回值 。
3)方法可以抛出异常。
4)支持泛型的返回值 。
5)需要借助FutureTask
类,比如获取返回结果。
class NumberThread implements Callable{
/** 2.实现Call方法,将此线程需要执行的操作声明在此方法Call()当中 */
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
/** 3.创建Callable接口实现类的对象 */
NumberThread numberThread = new NumberThread();
/** 4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTaskd的对象 */
FutureTask futureTask = new FutureTask(numberThread);
/** 5.将此FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法 */
new Thread(futureTask).start();
try {
/** 6.获取Callable中call() 方法的返回值 */
/** get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 */
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable
接口的方式创建多线程比实现Runnable
接口创建多线程方式强大?
call()
可以有返回值。
call()
可以抛出异常,被外面的操作捕获,获取异常的信息。
Callable
是支持泛型的
新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
1)提高响应速度(减少了创建新线程的时间)。
2)降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
3)便于线程管理 :
corePoolSize:
核心池的大小 。
maximumPoolSize:
最大线程数 。
keepAliveTime:
线程没有任务时最多保持多长时间后会终止。
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
/** 1.提供指定线程数量的线程池 */
ExecutorService service = Executors.newFixedThreadPool(10);
/** 设置线程池的属性 */
/** 由于service是ExecutorService接口,需要使用service的实现类对他进行属性设置 */
System.out.println(service.getClass());//查看service的实现类
/** 强转方式一: */
ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
// service1.setCorePoolSize(15);
/** 强转方式二: */
// ((ThreadPoolExecutor) service).setKeepAliveTime(1000);
/** 2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 */
service.execute(new NumberThread1());//适合使用于Runnable
service.execute(new NumberThread2());//适合使用于Runnable
service.shutdown();
}
}