多线程在操作同一个资源时,同一时刻只能有一个线程操作,其他线程等待这个线程操作结束后抢占操作这个资源,就是线程同步。
优点:线程同步可以保证多线程在操作同一个资源时,结果的正确性。
缺点:抢占式占用cpu处理器,只能保持一个线程执行,性能下降。
线程同步的实现 加锁
方式一:synchronized锁代码块。
public class TestSyn {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyTicket(),"黄牛1");
Thread thread2 = new Thread(new MyTicket(),"黄牛2");
Thread thread3 = new Thread(new MyTicket(),"观众");
thread1.start();
thread2.start();
thread3.start();
}
}
/*
*synchronized ("锁"){
}
//锁是一个Object对象类型
()里面的值:
1."锁"--固定值
2.this--对象锁 只能是同一个对象
这里是三个对象: 使用了三次 new MyTicket();
Thread thread1 = new Thread(new MyTicket(),"黄牛1");
Thread thread2 = new Thread(new MyTicket(),"黄牛2");
Thread thread3 = new Thread(new MyTicket(),"观众");
这里是一个对象:使用了一次 new MyTicket(); 注意两次 new Thread()只是代理了一个MyTicket对象申请 了两个线程 比喻为一个人有两个账号买票
MyTicket myTicket = new MyTicket();
Thread thread1 = new Thread(myTicket,"观众1");
Thread thread2 = new Thread(myTicket,"观众2");
Thread thread3 = new Thread(new MyTicket(),"观众");
*/
class MyTicket implements Runnable{
static int num = 1;
@Override
public void run() {
while(num<=1000){
synchronized ("锁"){//锁是一个Object对象类型 ()里面的值:"锁"--固定值、this--对象锁
if (num<=1000){
System.out.println(Thread.currentThread().getName()+"买了第"+num+"张票");
num++;
}else{
System.out.println("票售完了");
}
}
}
}
}
方式二:synchronized锁方法 [1]不建议结合run()修饰使用 [2]锁就是this
public class TestSyn2 {
public static void main(String[] args) {
MyTicket2 myTicket2 = new MyTicket2();
Thread thread1 = new Thread(myTicket2,"黄牛");
Thread thread2 = new Thread(myTicket2,"观众");
thread1.start();
thread2.start();
// //下面代码配合demo()01
// MyTicket2 myTicket3 = new MyTicket2();
// MyTicket2 myTicket4 = new MyTicket2();
// Thread thread3 = new Thread(myTicket3,"黄牛");
// Thread thread4 = new Thread(myTicket4,"观众");
// thread3.start();
// thread4.start();
}
}
class MyTicket2 implements Runnable{
//代表的卖出的票数
static int num = 1;
public void run(){
while(num<=500){
//demo01();
demo02();
}
}
/*
synchronized 修饰成员方法 锁的是this 要锁的对象必须是同一个对象
synchronized 修饰静态方法 当前锁的类锁 传递对象的字节码.class 同一个类创建出不同的对象
*/
public synchronized static void demo01() {
if (num<=500){
System.out.println(Thread.currentThread().getName()+"买了第"+num+"张票");
num++;
}else{
System.out.println("票售完了");
}
}
public synchronized void demo02() {
if (num<=500){
System.out.println(Thread.currentThread().getName()+"买了第"+num+"张票");
num++;
}else{
System.out.println("票售完了");
}
}
}
死锁
死锁产生的原因很多:1多个线程共享多个资源 2:多个线程都需要其他线程的资源,每个线程又不愿意或者无法放弃自己的资源
案例:
/**
* 死锁案例:小明和小红都要看电视 小明要看铠甲勇士 小红要看巴啦啦小魔仙 但是只有一个遥控器 小明拿到了遥控器壳 小红拿到了电池
* 小红和小明都不愿做出让步 然后两个人都看不到电视 电视就相当整个计算机 遥控器相当于CPU 小明和小红看电视的要求 相当于两个线程
* 两个线程都需要对方的资源才能上cpu运行 但是两个线程都不可释放需要的资源 cpu停滞 就是死锁
*/
public class TestSyn3 {
public static void main(String[] args) {
Thread thread1 = new Thread(new XiaoMing());
Thread thread2 = new Thread(new XiaoHong());
thread1.start();
thread2.start();
}
}
class XiaoMing implements Runnable{
@Override
public void run() {
synchronized ("遥控器壳"){
System.out.println("小明抢到了遥控器壳");
synchronized ("电池"){
System.out.println("我要看铠甲勇士");
}
}
}
}
class XiaoHong implements Runnable{
@Override
public void run() {
synchronized ("电池"){
System.out.println("小红抢到了电池");
synchronized ("遥控器壳"){
System.out.println("我要看巴啦啦小魔仙");
}
}
}
}
synchronize缺点:产生死锁的情况是没有办法处理,主要的原因是加锁和解锁的操作我们是不可以自己操作的,都是JVM完成。
方式三:Lock 可是首先手动加锁、解锁。 多线程使用的时候必须是同一个锁对象 如果代码出现了异常程序不会自动解锁,最好捕获异常。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestD {
public static void main(String[] args) {
new Thread(new TicketRunnable(),"1号窗口").start();
new Thread(new TicketRunnable(),"2号窗口").start();
}
}
class TicketRunnable implements Runnable {
static int num = 1;
//创建Lock锁
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (num<=1000){
lock.lock(); //给共享资源上锁
try{
if (num<=1000){
System.out.println(Thread.currentThread().getName()+"售出了第"+num+"张票");
num++;
}else {
System.out.println("票售完了");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
}
}
}
Lock锁解析:
lock()使用最多的一个方法,就是用来获取锁,如果锁已经被其他线程获取,则进行等待。
如果采用lock锁,必须主动去释放锁,并且发生异常时,不会自动释放锁,防止死锁的发生。通常配合异常使用:
lock.lock(); //给共享资源上锁
try{
}catch (){
}finally {
lock.unlock();//解锁
}
tryLock()
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),返回false。也就是说整个方法无论如何都会立即返回,拿不到锁时不会一直在那等待。
tryLock(long time,TimeUnit unit)
获取锁的方法和tryLock()类似,区别在于不会一直等待,参数有时间,如果一开始就拿到锁或者在等待区间拿不到锁就返回false,如果一开始就拿到锁或者在等待区间拿到锁就返回true。
lockInterruptibly()
lockInterruptibly()方法比较特殊,可以中断线程的等待状态。
ReentrantLock()可重入锁。
ReentrantLock(),意思是"可重入锁",可以被单个线程多次获取。
ReadWriteLock()
两个主要方法ReadLock()读锁 WriteLock()写锁;
获取读锁和写锁代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
public static void main(String[] args) {
//获取读和写的锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//获取读锁
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
//获得写锁
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
System.out.println("-----------使用接口形式进行表示--------------");
//使用多态 父类引用子类对象
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//获取读锁
Lock readLock2 = readWriteLock.readLock();
//获取写锁
Lock writeLock2 = readWriteLock.writeLock();
}
}
读写案例: 实现读写可以互斥进行,可以多个线程同时读,不能同时写,读写操作不能同时进行。
public class TestReadWriteLock2 {
public static void main(String[] args) {
new Thread(()->{
new Operator().read();
},"A").start();
new Thread(()->{
new Operator().read();
},"B").start();
new Thread(()->{
new Operator().write();
},"C").start();
new Thread(()->{
new Operator().write();
},"D").start();
}
}
class Operator{
//这个方法就是一个读方法
public void read(){
System.out.println(Thread.currentThread().getName()+"开始读操作");
try{
Thread.sleep(1000); //模拟读消耗的时间
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束读操作");
}
public void write(){
System.out.println(Thread.currentThread().getName()+"开始写操作");
try{
Thread.sleep(1000); //模拟读消耗的时间
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束写操作");
}
}
没有加锁可以看到运行结果读写顺序不一致。
加lock锁测试
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestReadWriteLock2 {
public static void main(String[] args) {
new Thread(()->{
new Operator().read();
},"A").start();
new Thread(()->{
new Operator().read();
},"B").start();
new Thread(()->{
new Operator().write();
},"C").start();
new Thread(()->{
new Operator().write();
},"D").start();
}
}
class Operator{
//这个方法就是一个读方法
static Lock lock = new ReentrantLock();
public void read(){
lock.lock();
System.out.println(Thread.currentThread().getName()+"开始读操作");
try{
Thread.sleep(1000); //模拟读消耗的时间
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束读操作");
lock.unlock();
}
public void write(){
lock.lock();
System.out.println(Thread.currentThread().getName()+"开始写操作");
try{
Thread.sleep(1000); //模拟读消耗的时间
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束写操作");
lock.unlock();
}
}
只能保证单个线程的读写互斥。
加读写锁
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock2 {
public static void main(String[] args) {
new Thread(()->{
new Operator().read();
},"A").start();
new Thread(()->{
new Operator().read();
},"B").start();
new Thread(()->{
new Operator().write();
},"C").start();
new Thread(()->{
new Operator().write();
},"D").start();
}
}
class Operator{
//这个方法就是一个读方法
static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void read(){
readWriteLock.readLock().lock();//读锁上锁
System.out.println(Thread.currentThread().getName()+"开始读操作");
try{
Thread.sleep(1000); //模拟读消耗的时间
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束读操作");
readWriteLock.readLock().unlock();//读锁解锁
}
public void write(){
readWriteLock.writeLock().lock();//写锁上锁
System.out.println(Thread.currentThread().getName()+"开始写操作");
try{
Thread.sleep(1000); //模拟读消耗的时间
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束写操作");
readWriteLock.writeLock().unlock();//写锁解锁
}
}