1、线程安全
为什么会出现线程安全问题?
- 需求:A线程将“Hello”存入数组;B线程将“World”存入数组。
- 线程不安全:
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
1.1 解决线程安全问题-同步锁
同步锁的关键词是synchronized。
有两种方式
1.同步代码块:在同一个时间里只允许一条线程进入同步代码块,当代码块运行完毕后,其他线程才会被CPU调度,必须保证监视器为同一个对象。
语法:
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}
package Day19_thread2.safe;/*
车站售票
解决线程安全的问题:
1.同步锁:synchronized,是一个关键词来的
1.1同步代码块:
在同一个时间里只允许一条线程进入同步代码块,当代码块运行完毕后,其他线程
才会被CPU调度,必须保证监视器为同一个对象
1.2同步方法:
被synchronized修饰的方法为同步方法,同一时间只允许一条线程调用,当同步方法
执行完后,其他线程才会被CPU调度,监视器为方法本身。要求保证调用同一个方法,
可以使用静态方法或者保证对象为同一个。
*/
public class Demo01 {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(new Task("窗口"+i));
thread.start();
//new Thread(new Task("窗口00"+i)).start();
}
}
}
class Task implements Runnable{
private static int ticket = 1000;
private static final Object obj = new Object();//静态属性的Object()
private String name;
public Task(String name){
this.name = name;
}
@Override
public void run() {
try {
safe();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void safe() throws InterruptedException {
while (true){//死循环
Thread.sleep(1);
synchronized (obj){
if (ticket > 0){
System.out.println(name+"正在售票"+ticket--);
}else {
System.out.println(name+"售票结束!!");
break;
}
}
}
}
}
2.同步方法:被synchronized修饰的方法为同步方法,同一时间只允许一条线程调用,当同步方法执行完后,其他线程才会被CPU调度,监视器为方法本身。要求保证调用同一个方法,可以使用静态方法或者保证对象为同一个。
语法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
// 代码(原子操作)
}
package Day19_thread2.safe;/*
使用同步方法,每一个线程实现的都不是同一个方法,方法为静态方法
同步方法:被synchronized修饰的方法为同步方法,同一时间只允许一条线程调用,当同步方法执行完后,其他线程
才会被CPU调度,监视器为方法本身。要求保证调用同一个方法,可以使用静态方法或者保证对象为同一个。
*/
public class Demo02 {
public static void main(String[] args) {
for (int i = 1; i < 11; i++) {
new Thread(new Task2(),"窗口"+i).start();
}
}
}
class Task2 implements Runnable {
private static int ticket = 1000;
private static final Object obj = new Object();//静态属性的Object()
private boolean flag = true;
@Override
public void run() {
try {
while (flag) {
Thread.sleep(1);
flag = safe();//定义了全局变量flag,需要赋值给safe(),不然程序会一直运行下去。
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized static boolean safe() throws InterruptedException {
if (ticket > 0) {
//Thread.currentThread().getName()获取程序名
System.out.println(Thread.currentThread().getName() + "正在售票" + ticket--);
} else {
System.out.println(Thread.currentThread().getName() + "售票结束!!");
return false;
}
return true;
}
}
案例:同步方法的应用
package Day19_thread2.safe;
//同步方法,对象为同一个
public class Demo03 {
public static void main(String[] args) {
//任务类对象只有一个
Task3 task3 = new Task3();
for (int i = 1; i < 11; i++) {
//Thread(Thread构造方法,线程名)
new Thread(task3,"窗口"+i).start();
}
}
}
class Task3 implements Runnable{
private static int ticket = 1000;
private boolean flag = true;
@Override
public void run() {
try {
while (flag){
Thread.sleep(1);
flag = safe();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//同步方法 --非静态
private synchronized boolean safe(){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"正在出售"+ticket--);
}else {
System.out.println(Thread.currentThread().getName()+"售票结束!!");
return false;
}
return true;
}
}
1.2线程通信方法
方法 | 说明 |
---|---|
public final void wait() | 释放锁,进入等待队列 |
public final void wait(long timeout) | 在超过指定的时间前,释放锁,进入等待队列 |
public final void notify() | 随机唤醒、通知一个线程 |
public final void notifyAll() | 唤醒、通知所有线程 |
wait()特点:
- 1、会使线程进入阻塞状态;
- 2、必须在synchronized中使用,没有则会出现IllegalMonitorStateException异常;
- 当线程调用wait()进入阻塞状态的时候,资源会被释放;
- 当线程调用wait()进入阻塞状态后,没有通过notify()或者notifyAll()唤醒线程,线程就会进入死锁状态。
package Day19_thread2.safe;
public class Demo05 {
public static final Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
/* //第一种方法
new Thread(new Task5()).start();
Thread.sleep(1000);
new Thread(new Task5()).start();
Thread.sleep(1000);
new Thread(new Task6()).start();*/
//会陷入阻塞状态
new Task5().start();
Thread.sleep(1000);
//会陷入阻塞状态
new Task5().start();
Thread.sleep(1000);
//唤醒陷入阻塞状态的线程
new Task6().start();
}
}
class Task5 extends Thread{
//private static Object obj =new Object();
@Override
public void run() {
/*try {
//在synchronized外面使用wait()
Demo05.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
synchronized (Demo05.obj){
System.out.println(Thread.currentThread().getName()+"线程开始");
try {
//会释放资源
Demo05.obj.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程结束!");
}
}
}
class Task6 extends Thread{
//private static Object obj =new Object();
@Override
public void run() {
synchronized (Demo05.obj){
System.out.println(Thread.currentThread().getName()+"线程开始");
//通过notify()唤醒进入阻塞状态的线程
//通过notifyAll()唤醒所有进入阻塞状态的线程
Demo05.obj.notifyAll();
System.out.println(Thread.currentThread().getName()+"线程结束!");
}
}
}
2、Lock接口
2.1 Lock
- JDK5加入,与synchronized比较,显示定义,结构更灵活。
- 提供更多实用性方法,功能更强大、性能更优越。
常用方法:
方法名 | 描述 |
---|---|
void lock() | 获取锁,如锁被占用,则等待。 |
boolean tryLock() | 尝试获取锁(成功返回true。失败返回false,不阻塞)。 |
void unlock() | 释放锁。 |
package Day19_thread2.lock;
//Lock:互斥对象锁或者排它锁,与同步锁最大的区别是需要显示获取和释放锁,不容易出现死锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {
public static void main(String[] args) {
for (int i = 1; i < 11; i++) {
new Thread(new Task("窗口"+i)).start();
}
}
}
class Task implements Runnable{
private static int ticket = 1000;
private static String name;
private static Lock lock = new ReentrantLock();
public Task(String name) {
this.name = name;
}
@Override
public void run() {
try {
safe();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void safe() throws InterruptedException {
while (true){
Thread.sleep(1);
//获取锁
lock.lock();
try {
//wait()只能在synchronized中使用
//lock.wait();
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"正在出售"+ticket--);
}else {
System.out.println(Thread.currentThread().getName()+"售票结束!!");
break;
}
}finally{
//释放锁(关闭)
lock.unlock();
}
}
}
}
3、守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆。
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
package Day19_thread2.daemon;/*
* @author pyh
* @date 2020/12/10 0010 下午 5:40
* @version 1.0
* @since JDK1.8_241
守护线程:当一条线程被设置为守护线程时,其他线程执行完,守护线程则销毁
守护线程守护所有的线程
垃圾回收机制(GC)也是一条守护线程
*/
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
MyThread1 myThread1 = new MyThread1();
myThread1.setDaemon(true);
myThread1.start();
new MyThread2().start();
for (int i = 0; i < 150; i++) {
Thread.sleep(1);
System.out.println("主线程:"+i);
}
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 300; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程:"+i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程:"+i);
}
}
}
案例:
package Day19_thread2.daemon;/*
* @author pyh
* @date 2020/12/10 0010 下午 5:55
* @version 1.0
* @since JDK1.8_241
* 在B线程中设置A为守护线程
* 到底是B线程结束,A线程跟着结束
* 还是所有线程结束,A线程结束
*/
public class Demo02 {
public static void main(String[] args) {
//A线程的对象
MyThread3 myThread3 = new MyThread3();
//B线程
new MyThread4(myThread3).start();
//C线程
new MyThread5().start();
}
}
//A线程
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 400; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护A子线程:"+i);
}
}
}
//B线程
class MyThread4 extends Thread{
private Thread thread;
//通过构造方法接收A线程的对象
public MyThread4(Thread thread){
this.thread = thread;
}
@Override
public void run() {
//设置A线程为守护线程
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 200; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B子线程:"+i);
}
}
}
class MyThread5 extends Thread{
@Override
public void run() {
for (int i = 0; i < 300; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C子线程:"+i);
}
}
}
4、线程池
线程池:节省了线程的创建和销毁
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
Executors工厂类:通过此类可以获得一个线程池。
方法名 | 描述 |
---|---|
newFixedThreadPool(int nThreads) | 获取固定数量的线程池。参数:指定线程池中线程的数量。 |
newCachedThreadPool() | 获得动态数量的线程池,如不够则创建新的,无上限。 |
newSingleThreadExecutor() | 创建单个线程的线程池,只有一个线程。 |
newScheduledThreadPool() | 创建固定大小的线程池,可以延迟或定时执行任务。 |
package Day19_thread2.pool;/*
* @author pyh
* @since JDK1.8_241
线程池:节省了线程的创建和销毁
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
/*//返回包含单条线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
Task1 task = new Task1();
for (int i = 1; i < 11; i++) {
//提交需要执行的线程任务到线程池中
pool.submit(task);
}
//关闭线程池
pool.shutdown();*/
/*//返回包含3条线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
Task1 task = new Task1();
for (int i = 1; i < 11; i++) {
//提交需要执行的线程任务到线程池中
pool.submit(task);
}
//关闭线程池
pool.shutdown();*/
//创建一个带缓冲区的线程池,会根据需求创建线程
ExecutorService pool = Executors.newCachedThreadPool();
Task1 task = new Task1();
for (int i = 1; i < 11; i++) {
//提交需要执行的线程任务到线程池中
pool.submit(task);
}
//关闭线程池
pool.shutdown();
}
}
class Task1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}