多线程的基本使用,及其方法,但关于多线程的并不止这些,关于多线程以后会持续更新!!!
目录
1. 多线程的创建
1.1 继承 Thread 类
(1)创建一个继承与Thread的子类
(2)重写Thread的run方法 -- >将创建此线程执行的操作声明在方法中
(3)创建Thread子类的对象
(4)通过对象调用start()方法
//1.创建一个继承与Thread的子类
class MyThread extends Thread{
//2.重写Thread的run方法
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0){
System.out.println(i);
}
}
}
}
public static void main(String[] args) {
//3.创建Thread子类的对象
MyThread m = new MyThread();
//4.通过对象调用start()方法
m.start();//启动当前线程,调用当前线程的方法
//不能直接对象.run方法调用启动线程
//再启动一个线程,输出偶数,不可以还让已经start的线程执行
MyThread m1 = new MyThread();
//m1.start();
//创建Thread类匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName() +" "+ i);
}
}
}
}.start();
}
1.2 实现Runnable接口
(1)创建一个实现了Runnable接口的类
(2)实现了Runnable中的抽象方法run()
(3)创建类的对象
(4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
(5)通过Thread类的对象调用start()
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2.实现了Runnable中的抽象方法run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i+" ");
}
}
}
public static void main(String[] args) {
// 3.创建类的对象
MThread m = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(m);
//在启动一个线程
t1.setName("线程1:");
Thread t2 = new Thread(m);
// 5.通过Thread类的对象调用start() ①启动线程 ②调用当前线程的run()方法
t2.setName("线程2:");
t1.start();
t2.start();
}
1.3 实现Callable接口
实现Callable接口,JDK5.0新增
实现Callable接口的方式创建多线程比实现Runnable接口实现多线程更强大?
- call有返回值
- call可以抛出异常
class NnmThead implements Callable{
//2.实现call方法,将此线程的操作写入call'方法中
public Object call() throws Exception {
int sum=0;
for (int i = 1; i <= 100; i++) {
if (i%2==0){
System.out.println(i);
sum +=i;
}
}
//此方法有返回值
return sum;
}
}
public static void main(String[] args) {
//3.创建Callable接口的实现类对象
NnmThead n = new NnmThead();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask f1 = new FutureTask(n);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,调用start方法,启动线程
new Thread(f1).start();
try {
//6.获取Callable方法中call的返回值
//get方法的返回值即为FutureTask构造器参数Callable实现类重写call()的返回值
Object sum = f1.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
1.4 线程池
线程池的好处:
(1)提高响应速度
(2)降低资源消耗
(3)偏于线程管理
//3.提供实现Runnable接口 或 Callable接口的方法
class NumThead implements Runnable{
@Override
public void run() {
for (int i = 1 ;i <= 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//将接口强转为类
//ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//service1.setCorePoolSize(15);
//可以设置线程池的属性
//2.执行指定线程的操作
service.execute(new NumThead());//适合适用于Runnable
//service.submit();//适合适用于Callable
//4.关闭连接池
service.shutdown();
}
2. Thead类中的方法
Thead类中的方法:
(1)start():启动当前线程,调用当前线程的run方法
(2)run():将创建线程的操作(业务)写在此方法中
(3)currentThread():静态方法,返回执行当前代码的线程
(4)getName():获取线程名
(5)setName():设置线程名,不设置会有默认名
(6)yield():释放cup的当前执行权
(7)join():进入指定线程,在线程A中调用线程B的join(),线程A就会进入阻塞状态
(8)stop():已过时,当执行此方法,强制结束此方法
(9)sleep():进入睡眠状态,单位为ms毫秒,在毫秒时间内,当前线程是阻塞状态
(10)isAlive:判断线程是否还存活
线程的优先级:
- MAX_PRIORITY
- MIN_PRIORITY
- NORM_PRIORITY(默认优先级)
设置线程的优先级:
- setPriority:设置优先级
- getPriority:获取优先级
- 高优先级的线程【大概率】抢占低优先级的线程执行权,但不意味着高优先级的线程先执行
相关方法演示:
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName() + ": "+i+" "+ Thread.currentThread().getPriority() );
//进入睡眠状态,单位为ms毫秒
// try {
// sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
//释放当前cup的执行权
if (i%20 ==0){
yield();
}
}
}
public static void main(String[] args) {
//通过构造器给线程命名
TheadMethod t = new TheadMethod("分线程");
//设置分线程的优先级
t.setPriority(Thread.MAX_PRIORITY);
//setName():设置线程名,不设置会有默认名
//t.setName("线程1:");
t.start();
//给主线程设置名
Thread.currentThread().setName("主线程:");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName() + ": "+i+" " + Thread.currentThread().getPriority());
}
//7.join():进入指定线程,在线程A中调用线程B的join(),线程A就会进入阻塞状态
// if (i ==20){
// try {
// //进入
// t.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
//判断指定线程是否还存活
//System.out.println(t.isAlive());
}
3. 买票问题
使用实现Runnable接口方式
但此方法会出现线程安全问题--->出现错票,重票等
解决方法:给线程加入锁,即使a线程被阻塞,其他线程也不可以执行
这里使用synchronized同步代码块解决
public class WindowsTestExer1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1-");
t2.setName("窗口2-");
t3.setName("窗口3-");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private int ticket = 100;
//创建一个随机锁
Object object = new Object();
@Override
public void run() {
while (true){
//操作共享数据的代码
//多个线程共用一个锁
//可使用当前对象来当锁
synchronized (this){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket );
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}else {
break;
}
}
}
}
}
4. 线程安全问题
在java中通过同步机制解决线程安全问题
(1)方法一:同步代码块
- 解决Runnable接口多线程
synchronized(同步监视器){
//需要被同步的代码
}
//需要被同步的代码:操作共享数据的代码
共享数据:多个线程共同操作的变量,数据。比如本问题的ticket
同步监视器: 俗称:锁。任何一个类的对象都可以充当锁
要求:多个线程必须公用同一把锁
(2)方法二:同步方法
- 同步方法处理Runnable接口的线程安全问题
如果共享数据的代码完整的声明在同一个方法中,这个方法可以放在同步中,就叫同步方法,在方法前声明synchronized
public synchronized void show(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket );
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
关于同步方法的总结:
- 任然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法,他的锁为this
- 静态方法的同步方法,锁为当前类
注意:
使用同步锁好处:解决了线程安全问题
坏处(局限性):操作同步代码时只能有一个线程,相当于是单线程
(1)解决线程安全的方式三:Lock锁 (JDK5.0以后出现的)
synchronized 与 lock的异同
- 相同点:都解决了线程安全问题
- 不同点:synchronized在执行完相应的同步代码以后自动的释放同步监视器
- lock需要手动的启动同步,同时要手动的结束同步
public class LockTest {
public static void main(String[] args) {
WindowTest w = new WindowTest();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1:");
t2.setName("窗口2:");
t3.setName("窗口3:");
t1.start();
t2.start();
t3.start();
}
}
class WindowTest implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//调用锁定方法:lock
lock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:" + ticket);
ticket--;
}else {
break;
}
}finally {
//调用解锁方法:unlock
lock.unlock();
}
}
}
}
5. 死锁问题
出现锁一般就要发生死锁问题
出现死锁,程序既不会停止运行,也不会抛出异常
在开发中要避免死锁
当一个线程等待另一个线程释放资源,而另一个线程有等待此线程释放资源时,就会发生死锁
现在先演示一下死锁
public class DeadLock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
6. 解决死锁
有死锁就要解决死锁
解决死锁就要先知道死锁的条件
(1)互斥条件:一个资源每次只能被一个线程使用。图上每条路上只能让一个方向的汽车通过,故满足产生死锁的条件之一
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。可以看出,图上每个方向的汽车都在等待其他方向的汽车撤走,故满足产生死锁的条件之二
(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。这里假设没有交警,那么没有人能强行要求其他方向上的汽车撤离,故满足产生死锁的条件之三
(4)循环等待条件:若干进程或线程之间形成一种头尾相接的循环等待资源关系。这个在图中很直观地表达出来了
解决死锁就要打破这四个死锁条件:
(1)打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;
(2)打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;
(3)进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低
(4)避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。
制作不易,记得点赞!!!!!!