概念
线程
负责java程序代码执行的控制单元,叫做线程。
并行和并发
并行是指两个或者多个事件在同一时刻发生。
而并发是指两个或多个事件在同一时间建个发生。
并行是发生在不同实体上的多个事件。
并发是同一实体上的多个事件。
普通解释:
并发:交替做不同事情的能力
并行:同时做不同事情的能力
专业术语:
并发:不同的代码块交替执行
并行:不同的代码块同时执行
多线程实现方式
继承Thread
-
定义类继承Thread。
-
复写Thread类中的Run方法。
-
调用线程的start()方法,启动线程。
示例
public class ThreadDemo {
public static void main (String[] args) {
MyThread myThread1 = new MyThread();
myThread1.setName("线程1");
MyThread myThread2 = new MyThread();
myThread2.setName("线程2");
myThread1.start();
myThread2.start();
}
}
public class MyThread extends Thread {
@Override
public void run () {
for (int i=0;i<10;i++){
System.out.println("线程id--->"+Thread.currentThread().getId()+"===线程名称----->"+Thread.currentThread().getName() + ": 执行成功" );
}
}
}
复写run()方法的原因:
Thread类用于描述线程。其中run()方法用于存储线程要运行的代码。
实现Runnable接口
- 定义类实现Runnable接口。
- 重写Runnable中的run()方法,放入将要运行的代码。
- 通过Thread类建立线程对象,将实现Runnable接口的子类对象作为实参传递给Thread类的构造函数,指定Thread要运行的run()方法为子类对象的run()方法。
- 调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法。
示例
public class PayRunnableDemo {
public static void main (String[] args) {
PayRunnable p = new PayRunnable();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
public class PayRunnable implements Runnable {
private static int stockNum = 100;
public void run () {
while (stockNum > 0){
System.out.println("线程:"+Thread.currentThread().getName()+"下单======剩余库存"+ (--stockNum));
}
}
}
实现方式和继承方式的区别:
实现方式避免了单继承的局限性,在定义线程时建议使用实现方式。
继承Thread类,线程代码存放在Thread类的run()方法中。
实现Runnable,线程代码存放在实现接口子类的run()方法中。
多线程安全问题
问题的原因:
多个线程在操作同一个共享数据时,一个多条语句的线程只执行了一部分时,另外一个线程开始执行,导致共享数据发生错误。
解决方法:
同一时刻,只允许一条线程操作共享数据。执行过程中,其他线程不能参与执行。
要解决上述问题,可以使用java中提供的同步机制(synchronized)。
synchronized
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1.普通同步方法,锁是当前实例对象
2.静态同步方法,锁是当前类的class对象
3.同步方法块,锁是括号里面的对象
同步代码块 : synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
synchronized(同步锁){
需要同步操作的代码
}
静态同步方法:synchronized(类.class)是全局锁,该类的所有实例对象共用一把锁。
synchronized(类.class){
需要同步操作的代码
}
同步方法 :使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){
可能会产生线程安全问题的代码
}
如果共享数据是同一个对象,以上三种方式都可以加锁。
但如果共享的数据是同一个类的不同实例对象的静态资源,只能使用静态同步方法加锁。
synchronized(this)和使用synchronized修饰方法是对象锁,如果有多个对象就有相对应的多个锁。
synchronized(类.class)是全局锁,不管有几个对象就公用一把锁。
Lock锁
Lock锁与synchronized都可以用于保证线程安全。
Lock是一个接口,而synchronized是关键字。
synchronized会自动释放锁,而Lock必须手动释放锁。
Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
synchronized能锁住类、方法和代码块,而Lock是块范围内的
常用方法:
public void lock() :加同步锁。
public void unlock() :释放同步锁
public boolean tryLock();尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false。
示例
public class PayRunnableDemo {
public static void main (String[] args) {
PayRunnable p = new PayRunnable();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
public class PayRunnable implements Runnable {
private static int stockNum = 100;
private Lock lock = new ReentrantLock();
public /*synchronized*/ void run () {
lock.lock();
while (stockNum > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+"下单======剩余库存"+ (--stockNum));
}
lock.unlock();
}
}