概述
Java实现多线程有两种方法:
1,继承Thread
2 , 实现 Runnable接口
举例:
public hello extends Thread{
public hello();
public hello("name"){
this.name=name;
}
public void run(){
for(int i=0;i<3;i++){
System.out.println(name+" "+i);
}
}
public static void main(String args[]){
hello h1 = new hello("a"); //自己可以实例化自己
hello h2 = new hello("b");
h1.run();
h2.run();
}
private String name;
}
运行后发现结果为:
a 0
a 1
a 2
b 0
b 1
b 2
我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。
将h1.run()与h2.run();换成h1.start() h2.start();
输出的结果每次都不一样。因为需要用到CPU的资源
注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。
2.实现Runnable类:
通过实现Runnable接口类作为一个线程的目标对象。此方法用Runnable目标对象初始化Thread类
new Thread(Runnable target);然后通过此实例调用start(),只有Thread类才有start()方法
调用的是Runnable接口的run,但启动是包装在Thread类中启动
public hello implements Runnable{
public hello(String name){
this.name=name;
}
public void run(){
for(int i=0;i<3;i++)
System.out.println(name+" "+i);
}
public static void main(String argu[]){
hello h1 = new hello("a");
hello h2 = new hello("b");
Thread demo = new Thread(h1);
Thread demo1 = new Thread(h1);
demo.start();
demo1.start();
}
private String name;
}
注意:一个类实现了Runnable并不是说明它一定是在多线程中运行,只有Thread类才是在多线程中运行,故把其作为参数传递给Thread才可以启动多线程来执行。
继承Thread类时,修改线程的名称。通过调用setName()或者构造方法传递,获得线程的名字通过getName()
而针对实现Runnable接口的这种方式,没有办法获取名称(当然通过Thread包装之后就可以调用setName了),所以,Java就提供了一个静态方法,获取当前正在执行的线程对象。
public static Thread currentThread()
//关于选择继承Thread还是实现Runnable接口?
//其实Thread也是实现Runnable接口的:
class Thread implements Runnable {
//…
public void run() {
if (target != null) {
target.run();
}
}
}
其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。
问题1:使用Thread子类创建线程的优点是?
可以在子类中增加新的成员变量,使线程具有某种属性;可以在子类中新增加方法,使线程具有某种功能
public class Test_Thread implements Runnable {
public void run(){
for(int i =0;i <10;i ++){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖"+ this .ticket --+"号票" );
}
}
}
public static void main(String args[]){
Test_Thread tt= new Test_Thread();
//Thread第一个参数是Runnable, 第二个参数线程名称
Thread demo= new Thread(tt ,"1窗口" );
Thread demo1= new Thread(tt ,"2窗口" );
Thread demo2= new Thread(tt ,"3窗口" );
demo.start();
demo1.start();
demo2.start();
}
private int ticket =5;
}
程序的问题:
A:相同的票卖了多次。
- CPU的每一次执行,必须是一个原子性的操作。这个操作是不能在分割的。
- int i =10;其实是两个原子性的操作(定义和赋值) 这样的话,tickets–其实也将不是一个原子性的操作。
- 可能在操作的中间部分,被其他的线程给执行了。这样就会有相同的票执行了多次。
B:出现了负数票的情况。
1. 因为线程的随机性。
问题:怎么样改进上面的问题呢???
只需要把多线程环境中,操作共享数据的操作给变成单线程的就没有问题了。
* Java针对这种情况,就提供了同步技术:同步代码块。
* 格式:
* synchronized(对象) {
* 需要被同步的代码。
* }
问题:
* A:对象?
* 如果不知道用哪个对象,就用Object对象。
* B:需要被同步的代码?
* 哪些代码导致出现了问题,就把哪些代码给同步起来。
哪些代码会出问题呢?
* 有共享数据。
* 针对共享数据有多条语句操作。
加入同步后,居然还有问题,为什么呢?
public void run() {
while (true) {
synchronized (new Object()) {
if (piao > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "号窗口正在卖" + (piao--) + "号票");
}
}
}
}
- 同步代码块中的对象针对多个线程必须是同一个。
- 其实这个对象被称为同步锁对象。
private static int piao = 10;
//创建同步锁对象
private Object obj = new Object();
public void run() {
while (true) {
// 看到obj就知道这是锁对象。假设t1进去前,obj这个锁对象表示开的状态。
synchronized (obj) {
// t1进来,就把锁对象的状态改为关的状态。多个线程必须使用同一个锁对象
if (piao > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "号窗口正在卖" + (piao--) + "号票");
}
}
}
}
注意同步方法(内有一个隐含的this,也就是说锁对象是this)
静态方法的锁对象是谁呢?
* 当前类的字节码文件对象。
同步代码块的锁对象可以是任意对象.
JDK5后的新特性:
Lock接口:实现类ReentrantLock
// 定义一个锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
// 释放锁
lock.unlock();
}
}
一个完整的多线程的输入输出问题:(Student类省掉):
public class setStudent implements Runnable {
private Student s;
private int x = 0;
// 通过构造方法传递值
public setStudent(Student s) {
this.s = s;
}
public void run() {
while (true) {
// 同步锁,setStudent()与getStudent()的锁对象保持一致,都是测试类中的s
synchronized (s) {
// 这个才是重点,多线程的 等待唤醒机制
if (s.Judge()) {
// s.Judge为student自定义的方法,flase表示没有值,此处判断当没有值的时候就不进入if而去等待
try {
s.wait(); //在这里等待了,释放锁对象,将来它获得执行权的时候,哪里睡的,哪里醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有值,设置值
if (x % 2 == 0) {
s.setName("wang");
s.setAge(17);
} else {
s.setName("xi");
s.setAge(19);
}
x++;
// 当设置完值后,s.judge()就成此true
s.setJudegTrue();
// 唤醒等待线程
s.notify(); // 唤醒等待的线程,唤醒其他的线程,不代表其他的线程能够立即执行。
}
//然后重新强执行器
}
}
}
public class getStudent implements Runnable {
private Student s;
// 通过构造方法传递值
getStudent(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
// 同步锁,setStudent()与setStudent()的锁对象保持一致,都是测试类中的s
synchronized (s) {
// 这个才是重点,多线程的 等待唤醒机制
if (!s.Judge()) {
// s.Judge为flase表示没有值,此处判断当有值的时候就不进入if而去等待
try {
s.wait(); //在这里等待了,释放锁对象,将来它获得执行权的时候,哪里睡的,哪里醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 有值,边打印值
System.out.println(s.getAge() + "+++" + s.getName());
// 打印完成后,自然又没有值了,便设置为false,表示没有值
s.setJudegFalse();
// 唤醒线程
s.notify();
}
//然后重新强执行器
}
}
}
public class test {
public static void main(String[] args) {
// 创建学生类
Student s = new Student();
// setStudent与getStudent分别继承了Runnable接口,并且可以通过构造方法传递值
setStudent set = new setStudent(s);
// 这种传递值的方法以后会常见到,保证了两个对象内的对象值保持一致
getStudent get = new getStudent(s);
// 创建Thread类来包装Rubbable接口的实现类的实例
Thread t1 = new Thread(set);
Thread t2 = new Thread(get);
// 启动线程
t2.start();
t1.start();
}
}
让线程休眠的两种方法sleep和wait:
sleep()必须制定休眠时间,而且不会释放锁对象。
wait()可以制定时间,也可以不制定,会释放锁对象。
线程醒来之后并不会立即执行,而是加入队列中等待CPU的调度。
线程的死锁:
public class DieLock extends Thread {
private boolean judge;
DieLock(boolean judge) {
this.judge = judge;
}
public void run() {
if (judge) {
while (true) {
//拿到objA锁
synchronized (MyLock.objA) {
System.out.println("if objA");
//此时需要objB锁。但objB锁没有释放,线程死锁
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
}
} else {
while (true) {
//拿到objB锁
synchronized (MyLock.objB) {
System.out.println(" else objB");
//此时需要objA锁。但objA锁没有释放,线程死锁
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
}
public class MyLock {
static final Object objA = new Object();
static final Object objB = new Object();
}
public class Test {
public static void main(String[] args) {
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
d1.start();
d2.start();
}
}
线程死锁的另一种情况:
分析上面的线程死锁的原因:
main线程访问Thread_Died.str的值。此时str尚未被初始化。则main线程开始对该类执行初始化。初始化包括以下两个步骤:
1)为静态变量分配空间
2)调用静态初始化块的代码为静态变量赋值
程序为str分配空间后执行初始化,即执行静态代码块的内容,此时str的值为null。。而静态代码块创建并启动了一条新线程,并调用了线程的join(),这意味着main线程必须等待新线程执行结束后才能向下执行。而在新线程中程序试图使用str的值。。。这时问题出现了:
Thread_Died类正由main线程执行初始化,因此新线程会等待main线程对Thread_Died类执行完初始化后才能使用str的值。
则main线程由于join()方法等待新线程的结束,而新线程由于输出str的值要等待main线程的介绍。则发生死锁。
避免上述代码的死锁:由于上述代码死锁的原因是调用了join()方法。把join代码注释掉即可避免死锁。
当把join()方法注释掉之后,通过上面的分析则先输出main线程的内容,然后为新线程的内容
如果我们希望先输出新线程的内容,则添加sleep()方法让main线程休眠即可。
不管是注释掉join()方法还是sleep()两个都不会输出新线程中被赋值的值,因为mian线程的执行初始化后没有调用join()方法,新线程处于就绪状态,还未进入运行状态,如果此时新线程进入了运行状态,当在操作str的值的时候新线程还是会放弃资源来让main执行初始化。main线程会继续执行初始化。故输出都不是新线程中的复制语句.当输出完成后新线程的赋值没有了意义。
而执行sleep()让新线程立即启动后输出的依然不是新线程中的赋值语句:因为当新线程要操作str的值时,该值还没有被初始化。因此新线程不得不放弃执行来等待main线程执行初始化,则输出依然不是新线程中的赋值。。。当然把新线程中的赋值放在输出之前则程序会不同,但在join()时依然会死锁。。。当输出完成后新线程的赋值没有了意义
优先级和暂停yield(): setDaemon(); join();
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源,当然通过继承Thread然后定义static变量也能实现而我们一般避免使用static,因为它的生命周期太长了
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM就是在操作系统中启动了一个进程。
实现Runnable接口要获取当前线程对象,必须Thread.currentThread().getName()
而继承了Thread类的只需要this即可this. getName()
最后
以上就是超级外套为你收集整理的java 多线程机制简单总结的全部内容,希望文章能够帮你解决java 多线程机制简单总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复