java多线程

来源:互联网 发布:淘宝买情侣最好的店铺 编辑:程序博客网 时间:2024/05/16 17:04
------ android培训java培训,期待与您交流 ------

 黑马程序员 java多线程
多线程中的进程,线程概念:
进程:正在进行中的程序,如QQ,360杀毒软件。
线程:就是进程中一个程序执行控制单元,一条执行路径,线程负责的是应用程序的执行顺序。
什么情况下是多线程:一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序。
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
我们自定义线程时,线程要运行的代码都统一存放在了run方法中。
怎样自定义线程呢?
创建线程的第一种方式:继承Thread ,由子类复写run方法。
步骤:
1,定义类继承Thread类;
2,复写run方法,将要让线程运行的代码都存储到run方法中,这里因为Thread类并不知道子类要运行的具体代码,因此将run方法提供出来,让子类复写,用到了模板设计模式。
3,通过创建Thread类的子类对象,创建线程对象;
4,调用线程的start方法,这个方法的作用是开启线程,并执行run方法。
示例:创建一个线程,和主线程交替运行
package multiThread;
public class ThreadDemo {
public static void main(String[] args) {
new Mythread().start();
for (int i = 0; i <1000; i++) {
System.out.println("main thread run"+i);
}
}
}
class Mythread extends Thread{
@Override
public void run() {
for (int i = 0; i <1000; i++) {
System.out.println("my thread run"+i);
}
}
}
这个方法每次执行的结果都会不同,是因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。
要注意的是调用线程的是start方法,而不是run方法,否则就是普通对象调用,是单线程了。
线程的几种状态:
被创建:start()方法被调用后,这时线程就具备了执行资格;
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()
由于java不支持多继承,因此继承Thread创建线程并不是最好的选择。
另一种创建线程的方式:实现Runnable接口。
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(同样是用于封装线程要运行的代码)。
3,通过Thread类创建线程对象;
4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
  为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
通常创建线程都用第二种方式,因为实现Runnable接口可以避免单继承的局限性。
实现Runable接口的多线程卖票示例:
package multiThread;
public class TicketDemo {
public static void main(String[] args) {
Ticket t =new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private int ticket=100;
@Override
public void run() {
while(ticket>0){
System.out.println(Thread.currentThread()+"...."+ticket);
ticket--;
}
}
}
在上面的程序中,其实会有多线程的安全问题
多线程安全问题涉及到两个因素:
1,多个线程在操作共享数据,如上面的票资源。
2,有多条语句对共享数据进行运算,改变了资源数据。
  原因:多条语句执行共享资源时,一个线程只执行了一部分,又有另外的线程进来操作共享数据,导致共享数据的错乱。
  解决安全问题的原理:
    只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
  如何进行多句操作共享数据代码的封装呢?
    java中提供了一个解决方式:就是同步代码块。
格式:
synchronized(对象) { 
需要被同步的代码;
}
package multiThread;
public class TicketDemo {
public static void main(String[] args) {
Ticket t =new Ticket();
Thread t1=new Thread(t)
Thread t2=new Thread(t);
Thread t3=new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private int ticket=100;
@Override
public void run() {
synchronized (Ticket.class) {//同步代码块,保证数据安全
while(ticket>0){
System.out.println(Thread.currentThread()+"...."+ticket);
ticket--;
}
}
}
}
好处:解决了线程安全问题。
弊端:相对降低性能,因为判断锁需要消耗资源。
定义同步的前提:
1,必须要有两个或者两个以上的线程,才需要同步。
2,多个线程必须保证使用的是同一个锁。
同步的第二种表现形式:
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性,同步函数所使用的锁就是this锁。
当同步函数被static修饰时,静态同步函数的锁是该类的字节码文件对象。
单例设计模式中的懒汉式线程安全问题
当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作,所以容易出现线程安全问题。
package multiThread;
public class Single {
private Single(){}
private static Single s= null;
public static Single getInstance(){
s= new Single();
return s;
}
}
上面代码未同步,很容易创建多个对象,违背了单例设计模式。
为了解决这个问题,加入同步锁,解决安全问题,同时为了效率问题,通过双重判断的形式解决。
package multiThread;
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ 
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
同步死锁:通常只要将同步进行嵌套,同步函数中有同步代码块,同步代码块中还有同步函数。
也就是说一个线程要进入另一线程中需要获取对方锁,对方线程进入本方线程中也要获取本方锁,但两线程都持有锁不放
从而导致死锁,编程中要避免死锁的发生。
线程间通信
上面所说的线程都具有同样的动作,操作相同的资源,当线程的动作不同时,这就是线程通信
等待唤醒机制:涉及的方法:
wait:将同步中的线程处于冻结状态,释放了执行权,释放了资格,同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程,该方法不一定唤醒对方,多线程使用容易使线程都处于等待状态
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。 
2:因为这些方法必须要标示所属的锁。
经典的生产者消费者示例:
package multiThread;
public class Pro_Con {
public static void main(String[] args) {
Resource rs = new Resource();
new Thread(new Producer(rs)).start();
new Thread(new Producer(rs)).start();
new Thread(new Consumer(rs)).start();
new Thread(new Consumer(rs)).start();
}
}
class Resource {// 定义要操作的资源
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void inPut(String name) {
if (flag) {// 有资源时,输入线程(生产者)等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
count++;
System.out.println(Thread.currentThread().getName() + "生产了苹果第" + count+ "个");
flag = true;// 生产了一个,将资源置为true
this.notifyAll();// 唤醒所有线程
}
public synchronized void outPut() {
if (!flag) {// 没有资源时,输出线程(消费者)等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费了苹果第" + count+ "个");
flag = false;// 消费完了,将资源置为没有状态,即false
this.notifyAll();// 唤醒所有线程
}
}
class Producer implements Runnable {// 定义生产者
private Resource rs;
Producer(Resource rs) {
this.rs = rs;
}
@Override
public void run() {
while (true) {
rs.inPut("苹果");
}
}
}
class Consumer implements Runnable {// 定义消费者
private Resource rs;
Consumer(Resource rs) {
this.rs = rs;
}
@Override
public void run() {
while (true) {
rs.outPut();
}
}
}
多线程在JDK1.5版本升级时,推出一个接口Lock接口。
这个版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,使代码看起来更直观,它的出现就替代了同步。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而
是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的
方法 await()、signal()、signalAll()体现新版本对象的好处。
将生产者消费者案例按新特性实现:
package multiThread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class fPro_Con_2 {
public static void main(String[] args) {
Resource_ rs = new Resource_();
new Thread(new Producer_(rs)).start();
new Thread(new Producer_(rs)).start();
new Thread(new Consumer_(rs)).start();
new Thread(new Consumer_(rs)).start();
}
}
class Resource_ {// 定义要操作的资源
private String name;
private int count = 0;
private boolean flag = false;
Lock lock=new ReentrantLock();
Condition condition_pro =lock.newCondition();
Condition condition_con =lock.newCondition();
public  void inPut(String name) {
lock.lock();//将需要同步的代码锁上
try {
while(flag)
condition_pro.await();
this.name = name;
count++;
System.out.println(Thread.currentThread().getName() + "生产了苹果第" + count
+ "个");
flag = true;// 生产了一个,将标记置为true
condition_con.signalAll();// 唤醒所有线程
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//锁一定要释放
}
}
public  void outPut() {
lock.lock();
try {
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName() + "消费了苹果第" + count
+ "个");
flag = false;// 消费完了,将资源置为没有状态,即false
condition_pro.signalAll();// 唤醒所有线程
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
class Producer_ implements Runnable {// 定义生产者
private Resource_ rs;
Producer_(Resource_ rs) {
this.rs = rs;
}
@Override
public void run() {
while (true) {
rs.inPut("苹果");
}
}
}
class Consumer_ implements Runnable {// 定义消费者
private Resource_ rs;
Consumer_(Resource_ rs) {
this.rs = rs;
}
@Override
public void run() {
while (true) {
rs.outPut();
}
}
}
------ android培训java培训,期待与您交流 ------
0 0