1. 什么是线程
线程。又称轻量级进程。线程是进程中的一条执行路径,也是CPU的基本调单位。
若一个程序可同一时间执行多个线程,就是支持多线程的。
一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。
为什么使用多线程
充分利用cpu,提高程序的效率
2. java如何实现多线程
提供了三种实现多线程的方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
2.1 继承Thread类
语法:
public class MyThread extends Thread {
@Override
public void run() {
for (int a=0;a<20;a++){
//getName 获取线程名,默认的名称Thread-n,该方法必须在Thread类下才能使用
//Thread.currentThread():获取当前正在执行的线程对象
System.out.println(Thread.currentThread().getName()+"-------------------"+a);
}
}
}
public class TestT01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int a=0;a<20;a++){
System.out.println(Thread.currentThread().getName()+"-------------------"+a);
}
}
}
案例
public class SellTictet01 extends MyThread {
private int tictets = 20;
@Override
public void run() {
while (true) {
if (tictets > 0) {
tictets--;
System.out.println(Thread.currentThread().getName() + "买了一张票,剩余" + tictets + "张");
} else {
System.out.println(Thread.currentThread().getName() + "卖完了");
break;
}
}
}
}
public class Stest01 {
public static void main(String[] args) {
SellTictet01 sellTictet1 = new SellTictet01();
SellTictet01 sellTictet2 = new SellTictet01();
SellTictet01 sellTictet3 = new SellTictet01();
SellTictet01 sellTictet4 = new SellTictet01();
Thread thread1 = new Thread(sellTictet1);
Thread thread2 = new Thread(sellTictet2);
Thread thread3 = new Thread(sellTictet3);
Thread thread4 = new Thread(sellTictet4);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
2.2 实现Runnable接口
public class Myrunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"===="+i);
}
}
}
public class TestR01 {
public static void main(String[] args) {
Myrunnable myrunnable = new Myrunnable();
Thread thread = new Thread(myrunnable);
thread.start();
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"===="+i);
}
}
}
案例
public class SellTictet02 implements Runnable{
private int tict=100;
@Override
public void run() {
while (true) {
if (tict > 0) {
try {
//睡眠
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tict--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余" + tict + "张");
} else {
break;
}
}
}
}
public class Rtest01 {
public static void main(String[] args) {
SellTictet02 sellTictet = new SellTictet02();
Thread thread1 = new Thread(sellTictet, "窗口A");
Thread thread2 = new Thread(sellTictet, "窗口B");
Thread thread3 = new Thread(sellTictet, "窗口C");
Thread thread4 = new Thread(sellTictet, "窗口D");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
分析出超卖和重卖现象。这是因为多个线程共享一个资源,导致线程安全隐患问题,后期解决线程安全问题。后期我们可以使用锁解决
两种方式继承Thread和实现Runnnable 哪种方式比较好
使用第二种,第二种的扩展性更好。
2.3 实现Callable接口
public class MyCallable implements Callable {
//1到100的和
@Override
public Object call() throws Exception {
double sum=0;
for (int i=0;i<=100;i++){
sum+=i;
}
return sum;
}
}
public class TestC01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask task = new FutureTask(myCallable);
Thread thread = new Thread(task);
thread.start();
Object o = task.get();
System.out.println(o);
}
}
3.Thread类中常用的一些方法
3.1 线程休眠方法-static void sleep
public class MyThread extends Thread {
@Override
public void run() {
for (int a=0;a<20;a++){
try {
//休眠十毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-------------------"+a);
}
}
}
3.2 yield 当前线程让出cpu-参与下次的竞争
3,
public class MyThread extends Thread {
@Override
public void run() {
for (int a=0;a<20;a++){
//当前线程让出CPU参与下次竞争
//使用yield线程出现交换执行的频率高了。
Thread.yield();
System.out.println(Thread.currentThread().getName()+"-------------------"+a);
}
}
}
3.3 join加入当前线程上
插入的线程执行完毕主线程才会执行
public class TestT01 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.setName("线程A");
MyThread t2 = new MyThread();
t2.setName("线程B");
t1.start();
t2.start();
t1.join();
//t1加入主线程上,主线程需要等t1执行完毕后才会执行。
for (int a=0;a<20;a++){
System.out.println(Thread.currentThread().getName()+"-------------------"+a);
}
}
}
3.4 setDaemon()设置线程为守护线程
当所有线程执行完毕后,守护线程也会终止
public class TestT01 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.setName("线程A");
t1.setDaemon(true);
t1.start();
for (int a=0;a<20;a++){
System.out.println(Thread.currentThread().getName()+"-------------------"+a);
}
}
}
JDK--默认就要有一个守护线程,GC垃圾回收
4. 解决线程安全问题
4.1 什么情况下会出现线程安全问题
当多个线程操作同一个资源时,则出现线程安全问题。
4.2 java如何解决线程安全问题
提高了两种方法:第一种:使用synchronized自动锁
第二种:使用Lock手动锁
使用锁相当于把原来的异步转化为同步操作
使用synchronized关键字解决
synchronized(共享锁对象){
同步代码块}
解决超卖现象
public class SellTictet02 implements Runnable {
private int tict = 1000;
@Override
public void run() {
while (true) {
synchronized (this) {
if (tict > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tict--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余" + tict + "张");
} else {
break;
}
}
}
}
}
使用Lock手动
public class SellTictet02 implements Runnable {
private int tict = 1000;
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true)
try {
//加锁
l.lock();
if (tict > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tict--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余" + tict + "张");
} else {
break;
}
}finally {
//解锁
l.unlock();
}
}
}
在这有一个问题(synchronized和Lock都有这个问题),如果执行卖一百张票,只有先开启的线程再卖, 但卖一千张,甚至一万张,四个线程才能交替执行,根据ai的回答是因为这个,我也不知道,大家姑且看一下:
synchronized和Lock 的区别
syn可以使用代码块和方法。自动加锁和释放锁。不会出现死锁问题。
lock它只能使用在代码块中。需要手动加锁和释放锁。如果不释放锁,死锁问题。灵活。它的释放锁必须放在finally
5. 死锁
线程A拥有资源a,希望获得资源b,线程B拥有资源b,希望获得资源a。两个线程互相拥有对方希望获得的锁资源。可能会出现程序堵塞,从而造成死锁。
public class locka implements Runnable{
@Override
public void run() {
synchronized ("locka"){
System.out.println(Thread.currentThread().getName()+"拥有了锁a");
synchronized ("lockb"){
System.out.println(Thread.currentThread().getName()+"拥有了锁b");
System.out.println(Thread.currentThread().getName()+"拥有了锁a和b");
}
}
}
}
public class lockb implements Runnable{
@Override
public void run() {
synchronized ("lockb"){
System.out.println(Thread.currentThread().getName()+"拥有了锁b");
synchronized ("locka"){
System.out.println(Thread.currentThread().getName()+"拥有了锁a");
System.out.println(Thread.currentThread().getName()+"拥有了锁b和锁a");
}
}
}
}
public class test {
public static void main(String[] args) {
locka a = new locka();
lockb b = new lockb();
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.setName("线程a");
t2.setName("线程b");
t1.start();
t2.start();
}
}
解决:
- 不要使用锁嵌套。
- 设置超过时间。 --Lock类中tryLock。
- 使用安全java.util.concurrent下的类
6. 线程通信
wait方法和notify方法。
public class Mybank {
//余额
private double balance;
//状态 true 有钱 false 没钱
private boolean flag;
//银行存钱
public synchronized void cun(double money){
if (flag==true){
try {
//进入等待队列,并且释放拥有的锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存钱
balance += money;
flag = true;
//唤醒等等待队列中的某个线程
notify();
System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
}
//银行取钱
public synchronized void qu(double money){
if (flag==false){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance-=money;
flag = false;
notify();
System.out.println(Thread.currentThread().getName()+"取钱成功,余额为:"+balance);
}
}
//实现存钱
public class Cuntread extends Thread{
private Mybank mybank;
public Cuntread(Mybank mybank){
this.mybank=mybank;
}
@Override
public void run() {
for (int i=0;i<10;i++){
mybank.cun(1000);
}
}
}
//实现取钱
public class Qutread extends Thread {
private Mybank mybank;
public Qutread(Mybank mybank) {
this.mybank = mybank;
}
public void run() {
for (int i = 0; i < 10; i++) {
mybank.qu(1000);
}
}
}
//测试
public class test {
public static void main(String[] args) {
Mybank mybank = new Mybank();
Cuntread cuntread = new Cuntread(mybank);
cuntread.setName("我");
Qutread qutread = new Qutread(mybank);
qutread.setName("你");
cuntread.start();
qutread.start();
}
}
7.线程状态
NEW 新建状态
RUNNABLE 就绪状态和运行状态
BLOCKED 堵塞状态
WATTING 等待状态
TIMED_WATTING 时间等待
TERMINATED 终止
通过调用不同的方法相互转换