多线程问题
来源:互联网 发布:手机qq java 编辑:程序博客网 时间:2024/05/23 02:04
1、线程问题:
1.1实现多线程的方式:
方式一:继承Thread
1)为什么要继承Thread类?
在Java中使用Thread这个类对线程进行描述。而我们现在希望通过自己的代码操作线程,自己的代码应该需要和Thread类之间产生关系。这里我们采用的继承的关系。
当我们继承了Thread类之后,我们自己的类也就变成了线程类。我们自己的类就继承到了Thread类中的所有功能,就具备了操作线程的各种方法,并且自己的类就可以对线程进行各种操作(开启线程等)。
而我们自己定义的类称为了一个线程类,主要是因为可以复写run方法。
2)为什么要复写run方法?
为什么要使用线程:因为我们希望程序中的某段代码可以同时运行,提高程序的运行效率。
我们定义的类继承了Thread类之后,其实在Thread类中有个run方法,它是开启线程之后,就会直接去运行的方法。而Java在设计线程类(Thread)的时候,就已经明确了线程应该执行的某段代码需要书写在run方法中,也就是说在run方法中的代码开启线程之后才能正常的运行。
我们使用线程的目的是让线程执行后来自己程序中的某些代码,而Java中规定需要线程执行的代码必须写run方法中,Thread类中的run方法中并没有我们真正需要多线程运行的代码,而开启线程又要去运行run方法,这时我们只能沿用Thread类run方法的定义格式,然后复写run方法的方法体代码。
简单来讲:
设计Thread这个API的人,在设计的时候,只设计了如何启动线程,至于线程要执行什么任务,他并不知道。所以,他这样设计:就是start启动线程之后,JVM会自动的调用run方法。
因此,我们只要把自己的代码写到run方法中,就一定会被执行到。
3)为什么要调用start而不是run?
当书写了一个类继承了Thread类之后,这个子类也变成线程类。这时可以创建这个子类的对象,一旦创建Thread的子类对象,就相当于拥有了当前的线程对象。
创建Thread的子类对象,只是在内存中有了线程这个对象,但是线程还不能真正的去运行。
要让线程真正的在内存运行起来,必须调用start方法,因为start方法会先调用系统资源,启动线程。这样才能够在内存开启一片新的内存空间,然后负责当前线程需要执行的任务。
我们直接通过线程对象去调用run方法,这时只是对象调用普通的方法,并没有调用系统资源,启动线程,也没有在内存中开启一个新的独立的内存空间运行任务代码。只有调用start方法才会开启一个独立的新的空间。并在新的空间中自动去运行run方法。
注意:run方法仅仅是封装了线程的任务。它无法启动线程。
run:只是封装线程任务。
start:先调用系统资源,在内存中开辟一个新的空间启动线程,再执行run方法。
4)同一个线程对象是否可以多次启动线程?
不能。如果同一个对象多次启动线程就会报如下异常。
代码如下所示:
/*
需求:演示:获取和设置线程的名称。
获取线程的名字使用函数getName()
线程的默认名字是:Thread-x x是从0开始的递增数字
*/
//定义一个类继承Thread,这个类称为线程类
class MyThread2extends Thread {
// B:复写Thread类中的run函数
publicvoid run() {
// 这里就是线程要执行的代码
/*
*由于getName()函数属于Thread类中的函数,而Thread类属于MyThread2的父类
*所以直接通过getName()函数就可以获得线程的名字,哪个线程调用run函数,就会
*获得哪个线程的名字
*/
for (int i = 0; i <10; i++) {
System.out.println(super.getName()+"..."+i);
}
}
}
publicclass ThreadDemo2 {
publicstatic void main(String[]args) {
// C:创建线程类的对象
MyThread2 mt = new MyThread2();
// D:启动线程,执行任务
mt.start();
// 再创建一个线程对象
MyThread2 mt2 =new MyThread2();
// 再一次开辟一个新的线程
mt2.start();
for (int i = 0; i <10; i++) {
System.out.println(i);
}
}
}
方式二:实现Runnable接口
代码如下所示:
/*
* 演示实现多线程方式2:实现Runnable接口
* A:定义一个类来实现Runnable接口,这个类是任务类
* B:实现Runnable接口中的run函数
* C:创建任务类对象
* D:创建线程类Thread的对象,并把任务类对象作为参数传递
* E:启动线程
*/
//A:定义一个类来实现Runnable接口,这个类是任务类
class MyTask implements Runnable
{
//B:实现Runnable接口中的run函数
public void run() {
for (int i = 0; i <10; i++) {
/*
* 由于这个类是任务类,不是线程类了,方式2与方式1不同之处就是
* 将线程类和任务类分离了,所以要想获得调用run函数的线程类名字不能直接调用getName()函数了
* 要想调用线程类Thread中的getName(),那么必须获得线程类的对象
* 获得当前正在执行线程的对象可以通过currentThread()函数
*/
System.out.println(Thread.currentThread().getName()+"==="+i);
}
}
}
public class ThreadDemo3 {
public static void main(String[]args) {
//C:创建任务类对象
MyTask task = new MyTask();
// D:创建线程类Thread的对象,并把任务类对象作为参数传递
Thread t1 = new Thread(task,"锁哥");
Thread t2 = new Thread(task,"助教");
//E:启动线程
t1.start();
t2.start();
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
}
}
1.2、多线程安全问题解决
1、同步代码块
加同步格式:
synchronized(需要一个任意的对象(锁))
{
代码块中放操作共享数据的代码。
}
/*
* 售票,多个售票窗口卖100张票
*/
//定义线程任务类
class SellTicketTaskimplements Runnable
{
//定义100张票
private int tickets=100;
//定义一个对象充当同步代码块上的锁
private Objectobj = new Object();
//实现run函数
public void run()
{
//使用循环模拟一直卖票
while(true)//t1 t2
{
//为了解决多线程的安全问题,给操作的共享资源代码加同步
synchronized(obj)//t1进来 关上门 上锁,此时t2进不来
{
//判断是否还有余票
if(tickets>0)
{
//休眠1毫秒,模拟延迟
try {Thread.sleep(1);}catch (InterruptedException e) {e.printStackTrace();}
//有余票 使用打印语句来模拟卖票
System.out.println(Thread.currentThread().getName()+"出票:"+tickets);
//票数量-1
tickets--;
}
}
//t1出来,释放锁,打开门 其他线程可以进入
}
}
}
public class SellTicketDemo {
public static void main(String[]args) {
//创建线程任务类对象
SellTicketTask stt = new SellTicketTask();
//创建线程对象,四个线程模拟四个窗口
Thread t1 = new Thread(stt,"窗口1");
Thread t2 = new Thread(stt,"窗口2");
Thread t3 = new Thread(stt,"窗口3");
Thread t4 = new Thread(stt,"窗口4");
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
2、同步方法
如上图所示,如果一个方法进来后,直接就是同步,也就是说,这个方法的所有代码都需要被同步。此时我们可以考虑把同步直接加到方法上:
以上被synchronized关键字修饰的方法称为同步方法。
但是对于同步方法来说,并没有唯一的锁,这样会引发多线程安全问题。
上述代码封装到同步方法中后,会出现如下问题:
那么同步方法上的锁到底是什么呢?
2)同步方法上的锁
同步方法的锁,不需要我们指定。证明,同步方法有自己默认的锁。
这个锁肯定是一个对象。而这个对象是同步方法能拿到的。上述method方法是非静态方法,而任何非静态方法,都有一个隐含的对象,就是:this。
所以,我们推测:同步方法的锁就是this。
注意:
A:如果一个方法内部,所有代码都需要被同步,那么就用同步方法;
B:非静态同步方法的锁是this;
3、静态同步方法
(1)既然有非静态同步方法,那么肯定也会有静态同步方法。
将上述非静态同步方法改为静态同步方法,代码如下所示:
问题:非静态同步方法有隐式变量this作为锁,那么静态方法中没有this,那么静态同步方法中的锁又是什么呢?
2)静态同步方法上的锁
静态同步方法,没有this,所以不能用this来做锁。
静态方法,类加载的时候,就已经被加载了。这个时候,锁已经确定了。
静态同步方法的锁,肯定也是一个对象,而且这个对象应该是在类加载时就已经存在。
静态同步方法的锁是:当前类的字节码文件对象(Class对象)。
其实可以这么理解:什么是字节码文件对象呢?其实就是class文件,类名.class。比如这里,就是 SellTicketTask.class
获取Class对象的方式:类名.class;
说明:
类名.class整体表示一个对象,class是作为类名的一个静态属性存在。这个class属性是所有的类型都具备的,API不会去专门描述他,他本身就在所有的类型中存在。
代码如下所示:
总结:
同步代码块:锁是任意对象,但是必须唯一;
非静态同步方法:锁是this;
静态同步方法:锁是当前类的字节码文件对象;类名.class
1.3、同步的好处、弊端
线程安全问题出现的原因:
A:多个线程
B:多个线程操作共享资源
C:操作资源的代码有多行
同步的弊端和好处:
同步的弊端:使用同步之后,会影响程序的执行效率。
每次CPU运行到同步的地方,都需要去判断有没有线程在同步代码块中。
同步的好处:加了同步之后,可以保证多个线程操作共享数据的安全。
1.4死锁问题
死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象。
注意:在开发中一旦发生了死锁现象,不能通过程序自身解决。必须修改程序的源代码。
在开发中,死锁现象可以避免,但不能直接解决。当程序中有多个线程时,并且多个线程需要通过嵌套对象锁(在一个同步代码块中包含另一个同步代码块)的方式才可以操作代码,此时就容易出现死锁现象。
可以使用一个同步代码块解决的问题,不要使用嵌套的同步代码块,如果要使用嵌套的同步代码块,就要保证同步代码块的上的对象锁使用同一个对象锁(唯一的对象锁)
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题...
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 多线程问题
- 数据结构-二叉树-建立,输出,计算长度-C++
- 160个练手CrackMe-030
- 11.11考试总结
- 数据结构部分考点整理
- hdoj 1727 Hastiness
- 多线程问题
- OpenMAX编程-数据结构
- [LeetCode]Container With Most Water
- Android短信验证码
- java使用axios.js的post请求后台时无法接收到参数的问题
- spring-cloud中config配置中心使用(基本使用)
- ionic购物车
- ValueError: too many values to unpack
- 【Java学习4.5】数组类型