Java基础-多线程总结
线程常用方法
方法 | 说明 |
public static void sleep(long millis) | 当前线程主动休眠millis毫秒。 |
public static void yield() | 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。 |
public final void join() | 允许其他线程加入到当前线程中 |
Thread.setDaemon(true) | 守护进程,Thread(线程名),必须在线程启动之前调用 |
Thread.setPriority(int a) | 线程优先级,a为1-10 |
方法就不和大家演示了
如何使用多线程
- 继承Thread类
class MyThread extends Thread{
@Override
public void run() {
//编写线程中的操作
}
}
- 实现Runnable接口
class MyRunnable implements Runnable{
public void run(){
//多线程处理的逻辑代码
}
}
推荐使用Runnable
1.因为java中单继承,继承了Thread类就无法在继承其他的类了,使用Runnable接口更为灵活一点;
2.另外继承代表强关联,实现稍微弱一点;
执行线程的是start方法
案例
使用子线程打印 0-9
- 继承Thread
//继承Thread
public class ThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();//开启子线程
System.out.println("主线程结束");
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
//Thread.currentThread().getName(),当前线程的名字
System.out.println(Thread.currentThread().getName()+"---"+i);
}
System.out.println("子线程结束");
}
}
- 实现Runnable
//实现Runnable
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println("主线程结束");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i=0;i<10;i++){
//Thread.currentThread().getName(),当前线程的名字
System.out.println(Thread.currentThread().getName()+"---"+i);
}
System.out.println("子线程结束");
}
}
- 其他
public class RunnableDemo1 {
public static void main(String[] args) {
//使用匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
//执行代码
}
}).start();
}
}
两者运行结果
线程安全问题
- 什么是线程不安全
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程,才可以保证其正确性。
- 原子操作:不可分割的多步操作,被视作一个整体其顺序不可打乱或缺省
- 案例:车站有四个窗口共卖100张票
public class Sell2 {
public static void main(String[] args) {
/*车站四个窗口共卖100张票*/
MyRunnable2 runnable = new MyRunnable2();
Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字
//开启线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class MyRunnable2 implements Runnable {
int size = 100;
@Override
public void run() {
while (size>0){
try {
Thread.sleep(100);//让线程睡眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
size--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
}
}
}
运行结果
- 案例分析
假设窗口一拿到了最后一张票时,还没来得及size–,其他窗口已经进入了while(size>0)循环,所以导致上述问题
解决线程安全问题
- 使用同步代码块
- synchronized(临界资源对象){//原子操作}//(给临界资源加锁)
public class Sell2 {
public static void main(String[] args) {
/*车站四个窗口共卖100张票*/
MyRunnable2 runnable = new MyRunnable2();
Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字
//开启线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class MyRunnable2 implements Runnable {
int size = 100;
@Override
public void run() {
while (size>0){
try {
Thread.sleep(100);//让线程睡眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
if(size>0){
size--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
}
}
}
}
}
确保size>0 和 size–,是一个原子操作
- 使用同步方法
- synchronized 返回值类型 方法名称(形参列表){//原子操作}//对当前对象(this)加锁
public class Sell {
public static void main(String[] args) {
/*车站四个窗口共卖100张票*/
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字
//开启线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class MyRunnable implements Runnable {
int size = 100;
@Override
public void run() {
while (size>0){
try {
Thread.sleep(100);//让线程睡眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
private synchronized void sell() {
if(size>0){
size--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
}
}
}
不能 将while放入synchronized 修饰的方法里面,会导致只有一个线程工作
- 使用Lock
Lock没有自动获取锁和自动释放锁概念
使用void lock();//获取锁 void unlock();//释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Sell1 {
public static void main(String[] args) {
/*车站四个窗口共卖100张票*/
MyRunnable1 runnable = new MyRunnable1();
Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字
//开启线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class MyRunnable1 implements Runnable {
Lock lock = new ReentrantLock();
int size = 100;
@Override
public void run() {
while (size>0){
try {
Thread.sleep(100);//让线程睡眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();//获取锁
if(size>0){
size--;
System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
}
lock.unlock();//释放锁
}
}
}
- Lock常见的方法
方法 | 说明 |
void lock(); | 获取锁,锁被占用,则等待 会阻塞 |
boolean tryLock(); | 尝试获取锁,成功返回true,失败false,不阻塞 |
void unlock(); | 释放锁 |
- Lock与synchronized比较
synchronized | lock | |
实现 | jvm实现 | java代码实现 |
释放锁的时机 | 1.加锁的代码块执行完毕 2.异常 | finally中释放锁 |
线程是否一直等待 | 一直等待 | 尝试获取锁,非一直等待 |
类型 | 可重入,不可中断,非公平 | 可重入,可中断,可公平 |
方式 | 悲观锁 | 悲观锁 底层使用了cas |
- 补充
什么是重入锁
重入锁,就是指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。 (防止死锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RepetitionLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();//获取锁
System.out.println("第一此获取锁");
lock.lock();//再次获取同一个锁对象
System.out.println("第二此获取锁");
lock.unlock();//释放锁
System.out.println("释放第二次获取的锁");
lock.unlock();//释放锁
System.out.println("释放第一次获取的锁");
}
}).start();
}
}
死锁
一个简单的死锁案例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyDeadlock {
public static void main(String[] args) {
Lock leftLock = new ReentrantLock();//左锁
Lock rightLock = new ReentrantLock();//右锁
new Thread(new Runnable() {
@Override
public void run() {
leftLock.lock();//获取左锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"获取到了左锁");
rightLock.lock();//获取右锁
System.out.println(Thread.currentThread().getName()+"获取到了右锁");
rightLock.unlock();
leftLock.unlock();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
rightLock.lock();//获取右锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"获取到了右锁");
leftLock.lock();//获取左锁
System.out.println(Thread.currentThread().getName()+"获取到了左锁");
leftLock.unlock();
rightLock.unlock();
}
}).start();
}
}
运行结果:一直在运行
Lock 在这里知识简单的说了一下,大家要是对Lock感兴趣的话可以在多查查这方面的资料