线程的定义和创建
继承Thread类
• Thread类是Java提供的线程顶级类,继承Thread类可快速定义线程。
[案例]龟兔赛跑
package Multithreading.Thread;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 14:28
* 龟兔赛跑
*/
public class Test1 {
public static void main(String[] args) {
RunTest tortoise = new RunTest() ;
tortoise.setName("乌龟");
tortoise.start();
RunTest rabbit = new RunTest();
rabbit.setName("兔子");
rabbit.start();
}
}
class RunTest extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
//获得执行线程的名称
System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
}
}
}
使用匿名内部类实现
package Multithreading.Thread;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 14:47
* 龟兔赛跑
*/
public class Test1Demo {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}.start();
}
}
• run() 线程体,线程要完成的任务
• start() 启动线程,线程进入就绪队列,等待获取CPU并执行
实现Runnable接口
[案例]龟兔赛跑
package Multithreading.Runnable;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 14:54
*/
public class Test1 {
public static void main(String[] args) {
RunnerRunable R1 = new RunnerRunable();
RunnerRunable R2 = new RunnerRunable();
Thread T1 = new Thread(R1,"乌龟");
Thread T2 = new Thread(R2,"兔子");
T1.start();
T2.start();
}
}
class RunnerRunable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
}
}
}
使用匿名内部类实现
package Multithreading.Runnable;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 15:11
*/
public class Test1Demo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
}
}
}, "乌龟").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
}
}
}, "兔子").start();
}
}
两种方式的优缺点
方式1:继承Thread类
缺点:Java单继承,无法继承其他类;
优点:代码稍微简单
方式2:实现Runnable接口
缺点:代码略有繁琐;
优点:还可以去继承其他类,便于多个线程共享同一个资源
实现Callable接口
[案例]使用多线程获取随机数
package Multithreading.Callable;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 15:19
*/
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable my = new MyCallable();
//Callable类型---->FutureTask类型 implements RunnableFuture<V> extends Runnable
FutureTask<Integer> futureTask = new FutureTask<>(my);
Thread t = new Thread(futureTask,"随机数");
t.start();
Integer integer = futureTask.get();
System.out.println(integer);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = new Random().nextInt(10);
System.out.println(Thread.currentThread().getName() + "---" + i);
return i;
}
}
使用匿名内部类实现
package Multithreading.Callable;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 15:42
*/
public class Test1Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> test1 = new FutureTask<>(new Callable<>() {
@Override
public Integer call() throws Exception {
int i = new Random().nextInt(10);
System.out.println(Thread.currentThread().getName() + "---" + i);
return i;
}
});
new Thread(test1,"乌龟").start();
System.out.println(test1.get());
FutureTask<Integer> test2 = new FutureTask<>(new Callable<>() {
@Override
public Integer call() throws Exception {
int i = new Random().nextInt(10);
System.out.println(Thread.currentThread().getName() + "---" + i);
return i;
}
});
new Thread(test2,"兔子").start();
System.out.println(test2.get());
}
}
第三种方式:实现Callable接口
与实行Runnable相比, Callable功能更强大些
• 方法名不同
• 可以有返回值,支持泛型的返回值
• 可以抛出检查异常
• 需要借助FutureTask,比如获取返回结果
Future接口
• 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
• FutrueTask是Futrue接口的唯一的实现类
• FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
线程同步
基本概念
多线程在操作同一个资源时,同一时刻只能有一个线程操作,其他线程等待这个线程操作结束后抢占操作资源.
优点:
线程同步可以保证多线程在操作同一个资源时,结果的正确性
缺点:
多线程操作时,由于只能有一个线程操作,导致性能下降
如何实现线程同步
实现线程同步的办法就是加锁,在java中锁有很多种
同步代码锁
synchronized(obj){}
同步方法锁
private synchronized void makeWithdrawal(int amt){}
synchronized同步锁
锁介绍
在Java中每个对象或类都可以当做锁使用,这些锁称为内置锁。Java中内置锁都是互斥锁。也就是说一个线程获取到锁,其他线程必须等待或阻塞。如果占用锁的线程不释放锁,其他线程将一直等待下去。锁在同一时刻,只能被一个线程持有。
如果锁是作用于对象,称对象锁。如果锁作用整个类称为类锁。
synchronized介绍
1.synchronized是Java中的关键字。使用synchronized关键字是锁的一种实现。
2.synchronized的加锁和解锁过程不需要程序员手动控制,只要执行到synchronized作用范围会自动加锁(获取锁/持有锁),执行完成后会自动解锁(释放锁)。
3.synchronized可以保证可见性,因为每次执行到synchronized代码块时会清空线程区。
4.synchronized 会不禁用指令重排,但可以保证有序性。因为同一个时刻只有一个线程能操作。
5.synchronized可以保证原子性,一个线程的操作一旦开始,就不会被其他线程千扰,只能当前线程执行完,其他线程才可以执行。6.synchronized 在Java老版本中属于重星级锁(耗费系统资源比较多的锁),随着lava的不停的更新、优化,在lava8中使用起来和轻量级锁(耗费系统资源比较少的锁)已经几乎无差别了。
7.主要分为下面几种情况:
1.修饰普通方法,非静态方法(对象锁)需要在类实例化后,再进行调用
⒉修饰静态方法(类锁)静态方法属于类级别的方法,静态方法可以类不实例化就使用
3.修饰代码块(对象锁、类锁)
锁为固定值
当锁为固定值时,每个线程执行到synchronized代码块时都会判断这个锁是否被其他线程持有,哪个线程抢到先执行哪个线程,当抢到的线程执行完aynchronized代码块后,会释放锁,其他线程竞争,抢锁,抢到的持有锁,其他没抢到的继续等待.
语法:
synchronized(锁){
//代码
}
锁代码块添加的类型为Object类型
运行过程:
多线程执行时,每个线程执行到这个代码块是首先会判断是否有其他线程持有这个锁,如果没有,执行synchronized代码块.如果已经有其他线程持有锁,必须等待线程释放锁.当一个线程执行完成synchronized代码块时会自动释放锁持有的锁.
修饰代码块
synchronized 修饰代码块
锁是一个Object类型的 注意:需要保证多个线程中使用的是同一把锁才可以所主代码
A----固定值(字符串)
B----this称为对象锁 注意:操作线程传递的对象必须保证是同一个
C----Object.class 存放的是一个Class类型 称为类锁
package Multithreading.Synchronized;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 17:03
*/
public class Ale {
public static void main(String[] args) {
new Thread(new breath(), "A窗口").start();
new Thread(new breath(), "B窗口").start();
new Thread(new breath(), "C窗口").start();
new Thread(new breath(), "D窗口").start();
}
}
class breath implements Runnable {
static int s = 1;
@Override
public void run() {
while (s <= 5000) {
synchronized ("锁") {
if (s <= 5000) {
System.out.println(Thread.currentThread().getName() + ",卖出了: " + s + "张票");
s++;
}else{
System.out.println("票已售完");
}
}
}
}
}
修饰成员方法
synchronized修饰成员方法
[1]synchronized 不建议结合run()修饰使用
[2]synchronized 锁就是this
[3]因为当前锁是对象锁(this)所以必须保证使用的对象是同一个对象
package Multithreading.Synchronized;
import lombok.Synchronized;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 17:03
*/
public class AleSynizedClas {
public static void main(String[] args) {
Breathe A = new Breathe();
new Thread(A, "A窗口").start();
new Thread(A, "B窗口").start();
new Thread(A, "C窗口").start();
new Thread(A, "D窗口").start();
}
}
class Breathe implements Runnable {
static int s = 1;
@Override
public void run() {
while (s <= 5000) {
Demo();
}
}
public synchronized void Demo(){
if (s <= 5000) {
System.out.println(Thread.currentThread().getName() + ",卖出了: " + s + "张票");
s++;
}else{
System.out.println("票已售完");
}
}
}
修饰静态成员方法
synchronized修饰静态成员方法
[1]当前锁就是类锁 传递对象字节码.class
[2]同一个类创建出不同对象 他们对象的.getclass()是一样的
package Multithreading.Synchronized;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/10 17:03
*/
public class AleSynizedClas {
public static void main(String[] args) {
new Thread(new Breathe(), "A窗口").start();
new Thread(new Breathe(), "B窗口").start();
new Thread(new Breathe(), "C窗口").start();
new Thread(new Breathe(), "D窗口").start();
}
}
class Breathe implements Runnable {
static int s = 1;
@Override
public void run() {
while (s <= 5000) {
Demo();
}
}
public static synchronized void Demo(){
if (s <= 5000) {
System.out.println(Thread.currentThread().getName() + ",卖出了: " + s + "张票");
s++;
}else{
System.out.println("票已售完");
}
}
}
死锁
死锁产生的原因
1.多个线程共享多个资源
2.多个线程都需要其他线程的资源,每个线程又不愿意或者无法放弃自己的资源(锁的开关无法人为控制)
3.synchronized没有释放锁的方法,所以处理的时候只能尽量避免
线程/工作内存/主内存三者的关系
1.所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问.
2.每个线程都有一个独立的工作内存,用于存储线程私有的数据.
3.线程对变量的操作必须在工作内存中进行(线程安全问题的根本原因)
a.首先要讲变量从主内存拷贝到线程的工作内存中,不允许直接操作主内存中的变量
b.每个线程操作自己工作内存中的变量副本,操作完成后再将变量写回到主内存
c.多个线程对一个共享变量进行修改是,都是对自己工作内存中的副本进行操作,互相不可见,所以主内存最后得到的结果是不可预知的
d.不同的线程间并无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成.
JMM的三个特征(保证线程安全最基本的条件)
1.可见性
在java中,不同的线程拥有私有工作内存,当线程需要修改某个变量时,不能直接去操作主内存中的变量,而是需要将这个变量读取到该线程的工作内存中,当该线程修改其变量副本后,其他线程并不能立刻读取到新值,需要将修改后的值刷新到主内存中,其他线程才能从主内存读取到修改后的值.
原因:工作内存和主内存存在同步延迟
解决方案:
1.volatile:可以保证内存可见性
规定线程每次修改变量副本后立刻同步到主内存中
规定霞新城每次使用变量前,先从知足内存中拷贝最新的值到工作内存中,用于保证能看见其他线程对变量修改的最新值
2.synchronized:同步锁
3.Lock锁
注:volatile可以保证可见性,但是假设多线程同事在做a++,在线程A修改共享变量从0到1的同事,线程B已经正在使用值为0的变量,所以正式后可见性就无法发挥作用,线程B将其值修改为1,所以最后的结果是1而不是2.
package Multithreading.JMMthreeFeatures;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/11 15:05
*/
public class Visibility {
public static boolean flag = true;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
flag = false;
}
}
2.有序性
在单线程程序中代码逐行执行,但是在多线程并发时,程序的执行就可能出现乱序.
多线程执行程序是,为了提高性能,编译器和处理器常常会自动对指令做重排序,目的是进行相关的优化,指令重排序
解决方法:
1.volatlile:可以保证有序性
在指令序列中插入内存屏障,防止指令重排序
2.sunchronzied:同步锁
3.Lock锁
package Multithreading.JMMthreeFeatures;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/11 15:11
*/
public class Order {
volatile static int a;
volatile static int b;
volatile static int c;
public static void main(String[] args) {
a = 10;
b = 20;
c = a + b;
System.out.println(c);
}
}
3.原子性
原子性指一个操作是不可中断的.及时是在多个线程一起执行的时候,一个线程的操作一旦开始,就不会被其他线程干扰,只能当线程执行完,其他线程才可以执行
解决方案:
1,.synchronized:同步锁
2.CAS
3.LOCK锁
package Multithreading.JMMthreeFeatures;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/11 15:38
*/
public class Atomicity {
static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int j = 0; j < 10; j++) {
new Thread(()->{
for (int i = 0; i < 10000; i++) {
atomicInteger.incrementAndGet();
}
}).start();
}
Thread.sleep(1000);
System.out.println(atomicInteger);
}
}
volatille可以解决可见性,有序性,但是不能解决原子性
synchronized/Lock可以解决可见性,有序性,原子性
Lock锁
基于synchronized锁的一些缺点,JDK1.5中推出了新一代的线程同步方式:Lock锁,更强大,更灵活,效率也更高.
Lock锁实现线程同步
Lock锁
1.多个线程使用的时候必须保证是同一个锁对象
2.使用lock()进行代码加锁,unlock()进行代码解锁
3.如果代码出现了异常,程序不会自动的解锁,所以使用Lock的时候最好写到try -catch -finally中保证安全
package Multithreading.JMMthreeFeatures;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/11 15:56
*
* synchronized缺点:产生的死锁情况是没有办法处理的,主要的原因是加锁和解锁的操作我们是不可以自己操作,都是JVM完成
*
*/
public class Lock1 {
public static void main(String[] args) {
new Thread(new LockRunnable(), "窗口A").start();
new Thread(new LockRunnable(), "窗口B").start();
new Thread(new LockRunnable(), "窗口C").start();
new Thread(new LockRunnable(), "窗口D").start();
}
}
class LockRunnable implements Runnable {
static int num = 1;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (num <= 100) {
lock.lock();
try {
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + "卖出了" + num + "张票");
num++;
} else {
System.out.println("票已售完");
}
}catch (Exception e) {
e.printStackTrace();
}
lock.unlock();
}
}
}
Lock接口
Lock中声明了四个方法获取锁.
1.Lock()
lock()方法是平常使用最多的,就是用来获取锁,如果锁已经被其他县阿城获取,则进行等待.
采用lock()必须主动释放锁,并且在放生异常时,不会自动释放锁.一次你一般来说lock()必须在try-catch中进行,并且将释放锁的操作放在finally中进行,保证锁一定被释放,防止死锁的发生.
2.tryLock()
trylock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已经被其他线程获取),则返回false,也就是说这个方法无论如何都会立刻返回,拿不到锁时不会一直等待.
3.tryLock(long time,TimeUnit unit)
tryLock(long time,TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在这个方法拿不到锁是会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false.如果在等待期间内拿到锁就返回true.
4.lockInterruptibly()
lockInterruptibly()方法比较特殊,当通过这个方法获取锁时,如果鲜橙汁正在等待锁,则这个线程能够响应中断,即中断线程的等待时间,也就是说,当两个线程同时通过lockInterruptibly()想获取某个锁时,假若此时A获取到了锁,而线程B只有在等待,那么线程B调用threadB.interrupt()方法能够中断线程B的等待时间.
package Multithreading.JMMthreeFeatures;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/11 17:56
*/
public class LockDeadlock {
public static void main(String[] args) throws InterruptedException {
ReentrantLock y = new ReentrantLock();
ReentrantLock d = new ReentrantLock();
Thread th1 = new Thread(new XiaoMIngRun(y, d));
th1.start();
Thread th2 = new Thread(new XiaoBaiRun(y, d));
th2.start();
}
}
class XiaoMIngRun implements Runnable {
private ReentrantLock remoteControl;
private ReentrantLock cell;
public XiaoMIngRun(ReentrantLock remoteControl, ReentrantLock cell) {
this.remoteControl = remoteControl;
this.cell = cell;
}
@Override
public void run() {
remoteControl.lock();
System.out.println("小明抢到了遥控器,正准备抢电视");
cell.lock();
System.out.println("小明抢到了电池,正准备抢遥控器");
cell.unlock();
remoteControl.unlock();
}
}
class XiaoBaiRun implements Runnable {
private ReentrantLock remoteControl;
private ReentrantLock cell;
public XiaoBaiRun(ReentrantLock remoteControl, ReentrantLock cell) {
this.remoteControl = remoteControl;
this.cell = cell;
}
@Override
public void run() {
cell.lock();
System.out.println("小白抢到了电池,正准备抢遥控器");
if(remoteControl.isLocked()) {
remoteControl.unlock();
}
remoteControl.lock();
System.out.println("小白抢到了遥控器,正准备抢电池");
remoteControl.unlock();
if (!cell.isLocked()) {
cell.unlock();
}
}
}
5.ReadWriteLock
ReadWriteLock也是一个接口,在它里面只定义了两个方法:
public interface ReadWriteLock{
Lock redLock();
Lock writeLock();
}
package Multithreading;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author: sanm
* @Description: TODO
* @DateTime: 2022/10/11 20:56
*/
public class ReaderWriterLockTest {
public static void main(String[] args) {
new Thread(()->{
new Opertor().reader();
},"A").start();
new Thread(()->{
new Opertor().reader();
},"B").start();
new Thread(()->{
new Opertor().writer();
},"C").start();
new Thread(()->{
new Opertor().writer();
},"D").start();
}
}
class Opertor {
static ReadWriteLock readerWriterLock = new ReentrantReadWriteLock();
public void reader() {
readerWriterLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "开始读");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "读结束");
readerWriterLock.readLock().unlock();
}
public void writer() {
readerWriterLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "开始写");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "写结束");
readerWriterLock.writeLock().unlock();
}
}
读写锁使代码同时满足读和读之间共享
,写和写之间互斥
,读和写之间互斥
Lock锁和同步锁(sunchronized)的选择
1.类型不同
synchronized是关键字在.修饰方法,修饰代码块
Lock是接口
2.加锁和解锁机制同步
synchronized是自动加锁和解锁,程序员不需要控制
Lock必须有程序员控制加锁和解锁过程,解锁时,需要注意出现异常不会自动解锁