文章目录
- 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{
@Override
public 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;
@Override
public 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();
@Override
public 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;
@Override
public 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();
@Override
public 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();
}
}