① 操做共享数据的代码,即为须要被同步的代码。(不能包含代码多了,也不能包含代码少了)
② 共享数据:多个线程共同操做的变量。好比:ticket就是共享数据。
③ 同步监视器,俗称:锁。任何一个类的对象,均可以充当锁。
要求:多个线程必需要共用同一把锁。
补充:在实现Runnable接口建立多线程的方式中,咱们能够考虑使用this充当同步监视器。java
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(); } }
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(); } }
① 同步方法仍然涉及到同步监视器,只是不须要咱们显式的声明。
② 非静态的同步方法,同步监视器是: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(); } }
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(锁)
---JDK5.0新增并发
synchronized
与lock
的异同相同点:两者均可以解决线程安全问题。app
不相同:synchronized
机制在执行完相应的同步代码之后,自动的释放同步监视器,Lock
须要手动的启动同步(lock()
),同时结束同步也须要手动实现(unlock()
)。ide
建议(优先使用顺序):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
类中。this
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(); } }
新增方式一:实现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(); } }