文章目录
- 1. Runnable接口
- 2. 卖票案例
- 3. 同步代码块解决数据安全问题
- 4. 同步方法解决数据安全问题
- 5. 线程安全的类
- 6. Lock锁
1. Runnable接口
1. 创建线程的另一种方法是声明一个实现Runnable
接口的类,之后重写run()
方法,然后可以分配类的实例,在创建Thread
时作为参数传递,最后启动。
2. 具体实现Runnable
接口:(1) 定义一个MyRunnable实现Runnable接口。 (2) 在MyRunnable中重写run()方法。 (3) 创建MyRunnable类对象。 (4) 创建Thread类的对象,把MyRunnable对象作为构造方法的参数。 (5) 启动线程。
public class MyRunnable implements Runnable{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+":"+i);}}
}public class MyRunnableDemo {public static void main(String[] args) {//创建MyRunnable类对象MyRunnable my=new MyRunnable();//创建Thread类对象,把MyRunnable对象作为参数传进来//Thread(Runnable target)//Thread t1=new Thread(my);//Thread t2=new Thread(my);//Thread(Runnable target, String name)Thread t1=new Thread(my,"线程1");Thread t2=new Thread(my,"线程2");//启动线程t1.start();t2.start();}
}
3. 多线程的实现方案有两种:(1) 继承Thread
类。 (2) 实现Runnable
接口。
- 实现
Runnable
接口好处:避免了Java单继承的局限性;适合多个相同程序的代码去处理同一资源的情况,把线程和程序的代码、数据有效分离,较好地体现了面向对象的设计思想。
2. 卖票案例
1. 需求和思路:
2. 代码块:
public class SellTicket implements Runnable{//表示有100张票private int tickets=100;@Overridepublic void run() {while (true){if(tickets>0){System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");tickets--;}}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
3. 问题分析:看上面结果相同的票会出现三次,为什么呢?请参照下图理解,虽然加上了休眠时间,但是原理是一样的,都是线程抢占CPU执行权导致的。
3. 同步代码块解决数据安全问题
1. 如何解决多线程安全问题:(1) 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。 (2) Java提供了同步代码块的方式来解决。
2. 同步代码块格式:
3. 代码块举例:
public class SellTicket implements Runnable{//表示有100张票private int tickets=100;private Object obj= new Object();@Overridepublic void run() {while (true){synchronized (obj) {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
4. 同步的好处和弊端:(1) 好处:解决了多线程的数据安全问题。 (2) 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
4. 同步方法解决数据安全问题
1. 同步方法:就是把synchronized
关键字加到方法上。
2. 格式:修饰符 synchronized 返回值类型 方法名(方法参数){ }
。
3. 同步方法的锁对象:this
。
4. 代码块举例:
public class SellTicket implements Runnable{//表示有100张票private int tickets=100;private Object obj= new Object();private int x=0;@Overridepublic void run() {while (true){if(x%2==0) { //这里得是this,否则会出问题synchronized (this) {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}}else{sellticket();}x++;}}private synchronized void sellticket(){if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
5. 线程安全的类
6. Lock锁
1. 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
。
2. Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
3. Lock中获得锁和释放锁的方法:void lock()
:获得锁。void unlock()
:释放锁。
4. Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock
来实例化。
5. 代码块举例:
public class SellTicket implements Runnable{//表示有100张票private int tickets=100;private Lock lock=new ReentrantLock();@Overridepublic void run(){while (true) {lock.lock();if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}lock.unlock();}}
}public class SellTicketDemo {public static void main(String[] args) {SellTicket st=new SellTicket();Thread t1=new Thread(st,"窗口1");Thread t2=new Thread(st,"窗口2");Thread t3=new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}