Java并发编程入门

进程与线程的区别

进程是一段正在执行的程序,是资源分配的基本单元。线程是进程的一个执行单元,线程是轻量级的进程。一个程序中至少有一个进程,一个进程中至少有一个线程

实现线程的几种方式

  • 继承Thread类
public class TestThread extends Thread {

    @Override
    public void run() {
        System.out.println("继承Thread类");
    }
    public static void main(String[] args) {
        new TestThread().start();
    }
}
  • 实现Runnable接口
public class TestRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("实现Runnable接口" + Thread.currentThread().getName());
	}
	public static void main(String[] args) {
		// 这里其实是一个静态代理
        new Thread(new TestRunnable(), "实现了Runnable接口的线程").start();
    }
}
  • 实现Callable接口
public class TestCallable implements Callable<String> {
	@Override
	public String call() throws Exception {
	    return "success";
	}
	public static void main(String[] args) {
		// 创建服务
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<String> submit = executorService.submit(new TestCallable());
        try {
            String result = submit.get();
            System.out.println(result);
            executorService.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

线程状态

线程状态位于Thread类的枚举State中,总共有6中状态

  • NEW(还没有调用start()的状态)
  • RUNNABLE(调用start()后的状态)
  • BLOCKED(阻塞状态)
  • WAITING(等待状态)
  • TIMED_WAITING(一定时间的等待状态)
  • TERMINATED(终止装态)

线程常用方法

  • sleep()(是一个native修饰的静态方法),调用方式Thread.sleep(1000)
public static native void sleep(long millis) throws InterruptedException;
  • join()(实现线程的插队效果,当前线程等待另一个调用join()方法的线程执行结束后再往下执行)
  • wait()(让调用该方法的线程进入等待状态,当其他线程调用了notify()或者notifyAll()后会被唤醒)
  • notify()/notifyAll()(唤醒其他线程)
public class TestJoin implements Runnable {
	@Override
	public void run() {
	   System.out.println("测试join()实现线程插队" + Thread.currentThread().getName());
	}
	public static void main(String[] args) {
        Thread thread = new Thread(new TestJoin(), "线程vip");
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                try {
                    thread.start();
                    //开始join(),这时候main线程会暂停等待该线程结束后继续执行
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

线程的优先级

  • MIN_PRIORITY = 1
  • NORM_PRIORITY = 5
  • MAX_PRIORITY = 10

线程优先级的取值范围为(1-10),值越大,线程的优先级就越高,但是还是要根据cpu的调度

thread.setPriority(int newPriority);

守护线程

  • 用户线程(我们创建的普通线程)
  • 守护线程(用来服务于用户线程,当所有的用户线程都退出后,守护线程就没有守护对象了,也会随着JVM的退出而结束)。创建守护线程的方法如下:
Thread thread = new MyThread();
thread.setDaemon(true);
thread.start();

线程同步

如果多个线程同时读写共享变量,会出现数据不一致的问题,以下是测试示例:

public class Bank {
	public static void main(String[] args) {
		Account account = new Account(100);
		new DrawMoney(account, 50, "你").start();
		new DrawMoney(account, 100, "ta").start();
	}
}
class DrawMoney extends Thread {
    Account account;
    int drawMoney;
    int nowMoney;
    String name;

    public DrawMoney(Account account, int drawMoney, String name) {
        this.account = account;
        this.drawMoney = drawMoney;
        this.name = name;
    }

    @Override
    public void run() {
        if (account.money - drawMoney < 0) {
            System.out.println(name + "取钱时发现余额不足");
            return;
        }
        //模拟延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money = account.money - drawMoney;
        nowMoney = nowMoney + drawMoney;
        System.out.println(name + "取完钱后,卡里余额还有" + account.money);
        System.out.println(name + "手上的钱为" + nowMoney);
    }
}

class Account {
    int money;

    public Account(int money) {
        this.money = money;
    }
}

结果我们发现,账户余额不对的情况

在这里插入图片描述

解决线程安全的几种方法

  • synchronized(同步方法/同步代码块)

(1)非静态同步方法:锁的是this对象。同一个对象在两个线程中分别访问该对象的两个非静态同步方法,这时候如果该对象调用了一个synchronized方法,其它的synchronized方法的调用需要等待前一个方法执行结束后并释放锁资源之后才能执行

public static void main(String[] args) {
	Phone phone = new Phone();
	new Thread(() -> {
		try {
			phone.sendEmail();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}, "A").start();
	//为了让A线程先被调用,先让主线程睡眠1秒
	try {
		TimeUnit.SECONDS.sleep(1);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	new Thread(() -> {
		try {
			phone.sendSms();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}, "B").start();
}
class Phone {
    public synchronized void sendEmail() throws Exception {
    	//为了达到延时打印的效果
        TimeUnit.SECONDS.sleep(4);
        System.out.println("-------------------sendEmail");
    }

    public synchronized void sendSms() throws Exception {
        System.out.println("-------------------sendSms");
    }

    public void eat() {
        System.out.println("-------------------eat");
    }
}

因为A线程先被调用,获取this对象的锁资源,所以先打印发邮件,后打印发短信
在这里插入图片描述

(2)静态同步方法:锁的是类的Class对象。不同的对象在两个线程中调用不同的静态同步方法,如果一个对象调用静态同步方法,那么其它对象对静态同步方法的调用需要等待前一个方法执行结束后并释放锁资源之后才能执行

public static void main(String[] args) {
	Phone phone = new Phone();
	new Thread(() -> {
		try {
			phone.sendEmail();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}, "A").start();
	//为了让A线程先被调用,先让主线程睡眠1秒
	try {
		TimeUnit.SECONDS.sleep(1);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	new Thread(() -> {
		try {
			phone.sendSms();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}, "B").start();
}
class Phone {
    public synchronized static void sendEmail() throws Exception {
    	//为了达到延时打印的效果
        TimeUnit.SECONDS.sleep(4);
        System.out.println("-------------------sendEmail");
    }

    public synchronized static void sendSms() throws Exception {
        System.out.println("-------------------sendSms");
    }

    public void eat() {
        System.out.println("-------------------eat");
    }
}

因为A线程先被调用,获取Class对象的锁资源,所以先打印发邮件,后打印发短信
在这里插入图片描述

  • ReentrantLock

ReentrantLock类是可重入、互斥、实现了Lock接口的锁

public static void main(String[] args) {
	Phone phone = new Phone();
	new Thread(() -> {
		try {
			phone.sendEmail();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}, "A").start();
	//为了让A线程先被调用,先让主线程睡眠1秒
	try {
		TimeUnit.SECONDS.sleep(1);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	new Thread(() -> {
		try {
			phone.sendSms();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}, "B").start();
}


class Phone {

    ReentrantLock lock = new ReentrantLock();

    public void sendEmail() throws Exception {
        try {
            lock.lock();
            TimeUnit.SECONDS.sleep(4);
            System.out.println("-------------------sendEmail");
        } finally {
        	//需要及时释放锁资源,否则会出现死锁
            lock.unlock();
        }
    }

    public void sendSms() throws Exception {
        try {
            lock.lock();
            System.out.println("-------------------sendSms");
        } finally {
        	//需要及时释放锁资源,否则会出现死锁
            lock.unlock();
        }
    }

    public void eat() {
        System.out.println("-------------------eat");
    }
}

Condition实现生产者、消费者模式

public class ConditionTest {

    private int product_num = 30;

    ReentrantLock lock = new ReentrantLock();
    Condition producer_condition = lock.newCondition();
    Condition consumer_condition = lock.newCondition();

    public void producer() {
        try {
            lock.lock();
            //使用while防止虚假唤醒
            while (product_num > 20) {
                try {
                    producer_condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            product_num = product_num + 5;
            System.out.println(Thread.currentThread().getName() + "生产了之后还剩" + product_num + "个商品");
            consumer_condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public void consumer() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            lock.lock();
            while (product_num <= 5) {
                try {
                    consumer_condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "消费了之后还剩" + --product_num + "个商品");
            producer_condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionTest market = new ConditionTest();
        new Thread(() -> {
            while (true) {
                market.producer();
            }
        }, "生产者A").start();
        new Thread(() -> {
            while (true) {
                market.consumer();
            }
        }, "消费者A").start();
        new Thread(() -> {
            while (true) {
                market.consumer();
            }
        }, "消费者B").start();
        new Thread(() -> {
            while (true) {
                market.consumer();
            }
        }, "消费者C").start();
    }
}

打印的结果如下
Java并发编程入门_第1张图片

synchronized与lock的区别
(1)synchronized是Java关键字;Lock是Java的一个类
(2)synchronized无法获取锁的状态;Lock可以判断是否获取了锁
(3)synchronized会自动释放锁;Lock需要手动释放锁,如果没有释放锁,就会出现死锁
(4)synchronized是可重入锁,线程不可以中断,非公平锁;Lock也是可重入锁,默认是非公平锁,但是可以设置是否公平锁还是非公平锁
(5)公平锁:线程依次排队获取锁;非公平锁:线程不需要排队

  • volatile

volatile变量具有synchronized的可见性,有序性,但是不具备原子性。volatile变量可以用于保证线程安全,但是需要满足一定的条件:
(1)对变量的赋值不依赖与变量的当前值,例如i++这种操作,就不满足原子性,这个时候线程也是不安全的
(2)该变量没有包含在具有其他变量的不变式中

  • 原子变量

对于普通变量的操作不是原子的,所以存在线程安全问题,原子操作就是指将读取变量值、修改变量值看成一个整体来操作

class AtomicTest {

    private final AtomicInteger product_num = new AtomicInteger(30);

    public void consumer() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (product_num.intValue() > 5) {
            System.out.println(Thread.currentThread().getName() + "消费了之后还剩" + product_num.getAndDecrement() + "个商品");
        }
    }

    public static void main(String[] args) {
        AtomicTest market = new AtomicTest();
        new Thread(() -> {
            while (true) {
                market.consumer();
            }
        }, "消费者A").start();
        new Thread(() -> {
            while (true) {
                market.consumer();
            }
        }, "消费者B").start();
        new Thread(() -> {
            while (true) {
                market.consumer();
            }
        }, "消费者C").start();
    }
}

CountDownLatch

CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
实现原理:通过一个计数器来实现,计数器的初始值是线程的数量,每当一个线程执行完毕后,计数器的值就会-1,当计数器的值为0时,表示所有的线程执行完毕,然后在闭锁上等待的线程就会恢复工作。但是CountDownLatch是一次性,在程序执行完成后,计数器的值为0。

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println("学生" + Thread.currentThread().getName() + "离开了教室");
                countDownLatch.countDown();
            }).start();
        }
        //其它线程(这里指主线程)进入等待,等待上面的线程执行完毕之后,才回复执行
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "班长开始关教室门");
        System.out.println(countDownLatch.getCount());//此时计数器为0
    }
}

CyclicBarrier

CyclicBarrier这个类使一个线程等待其他线程各自执行完毕后再执行。实现原理:它的内部有一个计数器,当计数器的值不断减小为0时,所有阻塞的线程将会被唤醒。有区别的是,CountDownLatch的计数器有调用者来控制,CyclicBarrier的计数器由其自己内部控制。在程序执行完毕的时候,CyclicBarrier的计数器的值还是初始化时候的值,这就意味着,CyclicBarrier是可以循环使用的。

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(8, () -> System.out.println(Thread.currentThread().getName() + "====八仙过海各显神通"));
        for (int i = 1; i <= barrier.getParties(); i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "====" + finalI + "仙过海");
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        System.out.println(barrier.getParties());//计数器的值为8
    }
}

Semaphore

Semaphore是用于控制同时访问特定资源的线程的数量,它通过协调各个线程,保证合理的使用公共资源。

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢占了资源");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println(Thread.currentThread().getName() + "释放了资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            }).start();
        }
    }
}

ReadWriteLock

使写的操作同时只有一个线程,使读的操作同时可以有多个线程

public class ReadWriteLockTest {

    public static void main(String[] args) {

        MyResource resource = new MyResource();
        for (int i = 0; i < 5; i++) {
            final int index = i;
            new Thread(() -> resource.put(String.valueOf(index), String.valueOf(index))).start();
        }
        for (int i = 0; i < 5; i++) {
            final int index = i;
            new Thread(() -> resource.get(String.valueOf(index))).start();
        }
    }
}

class MyResource {
    private volatile Map<String, String> map = new HashMap<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void put(String key, String value) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始写入数据");
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入数据完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void get(String key) {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始读取数据");
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取数据完成" + value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}

你可能感兴趣的:(java,并发编程,多进程)