多线程
多线程的概述
了解多线程首先要了解并发和并行的简单概念。
并发和并行
并行:指两个或多个事件在同一时刻点发生;(例如多个运动员同时起跑)
并发:指两个或多个事件在同一时间段内发生。(例如在一个窄桥上,你先走完我再走)
并行是针对进程的,并发是针对线程的。
进程和线程:
再了解一下进程和线程的区别:
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。
多个线程之间是抢占资源的
当我们开启了多线程以后,多线程执行的任务交给cpu去执行,如果cpu是多核的,此时执行的就是并行(并行在多核上,一个核上运行一个
线程),如果是一个核,相当于在cpu里面执行线程也需要排队了。这个时候就是并发。
创建线程的方式:
直接继承Thread类,重写run方法
示例:
public class ThreadDemo extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(i + ".你好,来自线程" + Thread.currentThread().getName());
}
}
}
main方法中启动线程:
public class test {
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo();
Thread thread1=new Thread(td,"Thread-0");
Thread thread2=new Thread(td,"Thread-1");
thread1.start();
thread2.start();
}
}
可以看到不同线程之间是抢占cpu的
实现Runnable接口:
示例:
public class ThreadDemo implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(i + ".你好,来自线程" + Thread.currentThread().getName());
}
}
}
main方法启动:
public class test {
public static void main(String[] args) {
ThreadDemo td=new ThreadDemo();
Thread thread1=new Thread(td,"Thread-0");
Thread thread2=new Thread(td,"Thread-1");
thread1.start();
thread2.start();
}
}
两种线程的区别:
线程的状态:
线程调度的几个常用方法:
setPriority()
更改后的线程得到cpu资源的概率改变,但是不代表数值小的线程得不到cpu资源
sleep:
join和yield的区别:
一个代表阻塞,另一个还在就绪状态
join:
yield:
synchronized
学习synchronized前先看一道练习题:
卖票:
三个窗口售卖100张票,每卖一张票窗口休息一秒钟。
售票窗口:
package JavaAPI.Ch08.test;
public class Shou implements Runnable {
static int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖了第" + num + "张票");
num--;
}
}
}
}
测试类:
package JavaAPI.Ch08.test;
public class Test {
public static void main(String[] args) {
Shou s=new Shou();
Thread t1=new Thread(s,"窗口1");
Thread t2=new Thread(s,"窗口2");
Thread t3=new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
可以看到存在卖重复的票,而且最后会售卖负数的票,这是因为不同线程之间抢占cpu资源,在醒过来的时间不同,在控制台输出的时间不同,会出现读错的情况
synchronized 关键字就可以解决线程数据安全
synchronized 使用的前提:
解决思路:将共享数据的代码块锁起来,使在任意时刻时,只能有一个线程来修改数据
synchronized 使用:
package JavaAPI.Ch08.test;
public class Shou implements Runnable {
private int num = 100;
private Object o=new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖了第" + num + "张票");
num--;
}
}
}
}
}
synchronized 在方法上的使用:
线程安全的类:
StringBuffer,Vector,Hashtable
Hashtable和HashMap区别:
Lock锁
示例:
package com.Thread.maipiao2;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private int ticket = 100;
//设置锁
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
// 模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
//加锁
lock.lock();
//使用锁解决数据安全问题
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售卖了第" + ticket + "张票");
ticket--;
}
}finally {
//释放锁
lock.unlock();
}
}
}
}
生产者消费者概述:
Object提供的等待和唤醒方法:
在使用Object提供的等待和唤醒方法时需要在锁的方法中实现,需要在方法前加入synchronized 关键字