您的位置:首页 > 编程语言 > Java开发

Java线程笔记1

2014-10-24 08:27 113 查看
一、几个概念

1.进程

    一个正在执行中的程序
    每个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
2.线程

    进程中一个独立的控制单元,线程在控制着进程的执行,线程才是进程中真正执行的部分
    一个进程中至少有一个线程,但是可以拥有多条执行路径,即多个线程

3.多线程

    多个线程并发执行

4.多线程存在的意义

    多线程可以让程序产生同时运行效果,提高程序执行效率。

    以虚拟机为例,Java vm 启动的时候会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在main方法中,该线程称之为主线程。jvm启动时不止一个线程,除了主线程之外,还有一个线程专门负责垃圾回收机制。如果只有主线程没有垃圾负责垃圾回收的线程,一旦垃圾过多内存用完,程序将无法执行下去,抑或主线程停下来去处理垃圾,原本正在执行的程序将处于等待状态,影响效率。而多线程可以使多段代码同时执行,可以一边执行程序一边处理垃圾。

二、创建线程的两种方式
第一种:继承Thread类
    1.定义类继承Thread
    2.覆写Thread类中的run()方法
    3.调用线程的start方法

程序示例
<span style="font-size:14px;"><span style="font-size:12px;">class SubThread extends Thread{
/**
* 创建线程类的子类,重写run()方法
*/
//	String name;
public SubThread(String name){
//		this.name = name;
super(name);
}
//覆写run方法
public void run(){
for(int i=1; i<=600; i++)
//输出线程名以及运行的次数
System.out.println(Thread.currentThread().getName()+"SubThread run----"+i);
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
SubThread st1 = new SubThread("one---");//创建一个线程
st1.start();//开启线程
SubThread st2 = new SubThread("two+++");//创建一个线程
st2.start();//开启线程
//主线程代码部分
for(int i=1; i<=600; i++)
System.out.println("hello world!--"+i);
}

}</span></span>

public static Thread currentThread()   获取当前线程对象

public String getName()  获取线程名称

设置线程名称:setName方法或者构造函数
线程都有自己默认的名称,Thread-编号 该编号从0开始,如果不设置线程名就会采用这种默认的命名方式

运行截图

       每一次的运行结果都不一样



第二种:实现Runnable接口
   1.定义类实现Runnable接口
   2.覆盖Runnable接口中的run方法
   3.通过Thread类建立线程对象
   4.将Runnale接口的子类对象作为实际参数传递给Thread类的构造函数
   5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法

程序示例
<span style="font-size:14px;"><span style="font-size:12px;">/*
*  卖票的例子
*/
class Ticket implements Runnable{
private static int ticket = 100; //总票数
@Override
public void run(){
while(true){
if(ticket>0){
try {
Thread.sleep(10);//线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前线程名称以及售出的票
System.out.println(Thread.currentThread().getName()+"---sale---   "+ticket--);
}
}
}
}
public  class TicketDemo {
public static void main(String[] args) {
//创建Runable子类的对象
Ticket ticket = new Ticket();
//创建Thread类,将Runnable接口的子类对象传给它
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
//开启线程
t1.start();
t2.start();
}

}</span></span>


运行结果截图



这里涉及到的线程安全的问题将在后面解释

两种方式中都覆写了run方法了,为什么要覆盖run方法?

    Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,即run方法用于存储线程要运行的代码。复写run方法的目的是将自定义代码存储在run方法,让线程运行。

既然run方法用于存储线程要运行的代码,为什么要不直接调用run方法而是调用start方法?

    start方法有两个作用:启动线程,调用run()方法

调用start方法与run方法的区别
    start用于开启线程并执行该线程的方法。注意:重复调用start方法程序会抛出异常
    调用run与一般的对象调用没有区别,线程创建了,并没有运行,依然只有一个线程

继承方式和实现方式的区别
    继承Thread:线程代码存放在Thread子类的run方法中
    实现Runnable:线程代码存放在Runnable接口子类run方法中

实现方式方式的好处:避免了单继承的局限行,定义线程时,建议使用这种方式

两个程序每次运行的结果都不一样,这又是为什么呢?

    这和CPU的执行原理有关。  

    因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为看成是在互相抢夺CPU的执行权。

这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,由CPU决定。

[align=justify]三、线程的几种状态
    1.被创建:通过new关键字创建了Thread类(或其子类)的对象,等待被启动[/align]
[align=justify]    2.运行状态:具备运行资格和执行权[/align]
[align=justify]    3.临时阻塞状态:具备运行资格但是没有执行权[/align]
[align=justify]    4.冻结状态:线程因为调用sleep方法或者wait方法等进入阻塞,放弃CPU的执行权[/align]

[align=justify]    5.消亡状态:run方法结束或者线程调用了stop方法
[/align]
线程状态图



四、多线程安全问题

    卖票的程序中,票据出现了0号票,现实生活中是不应该出现0号票,这里就涉及到了线程安全的问题

产生的原因:
    当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
     对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。
Java中提供的解决方法----同步

    1.同步代码块

       用法:

        synchronized(对象){
            //需要被同步的代码
       }
      同步代码块解决线程安全的原因在于这个对象,好比一把锁,每个线程要执行被同步的代码都必须获取此对象的锁,如果这个对象的锁被其他线程获取就必须等待其他对象释放这个锁。

加入了同步的卖票的例子
<span style="font-size:14px;"><span style="font-size:12px;">/*
*  卖票的例子,已加入同步
*/
class Ticket2 implements Runnable{
private static int ticket = 100;   //总票数
Object obj = new Object();  //用于同步的对象
@Override
public void run(){
while(true){
synchronized(obj){   //加入同步
if(ticket>0){
try {
Thread.sleep(20);//线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前线程名称以及售出的票
System.out.println(Thread.currentThread().getName()+"---sale---   "+ticket--);
}
}
}
}
}</span></span>
程序运行截图



可以看到没有0号票了

2.同步函数
    用法:

        在函数返回值类型前加上synchronized关键字
    同步函数用的是哪一个锁?
        函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this
    如果同步函数被静态修饰后,使用的锁又是什么呢?
        静态函数使用的不是this锁,因为在静态方法中也不可以定义this。
        静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码对象  类名.class  ,该对象的类型是Class
        静态的同步方法,使用的锁是该方法所在类的字节码对象 ,即类名.class

示例:

<span style="font-size:14px;"><span style="font-size:12px;">/*
* 懒汉式单例设计模式的安全问题
*/
public 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;

}
}</span></span>


3.同步的前提:
    1).必须要有两个或者两个以上的线程
    2).必须是多个线程使用同一个锁
4.如何找到多线程中的安全问题

    1).明确哪些代码是多线程运行代码

    2).明确共享数据

    3).明确多线程运行代码中哪些语句是操作共享数据的

5.同步的好处与弊端

    好处:解决了多线程的安全问题
    弊端:多个线程都需要判断锁,较为消耗资源
五、死锁问题

死锁产生的原因:   

    同步中嵌套同步

死锁的例子
<span style="font-size:14px;"><span style="font-size:12px;">
/*
* 死锁的例子
*/
class MyLock {
static Object lockA = new Object();
static Object lockB = new Object();
}
class Test implements Runnable{
private boolean flag;
public Test(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized(MyLock.lockA){   //上lockA锁
System.out.println("if lockA");
synchronized(MyLock.lockB){   //上lockB锁
System.out.println("if lockB");
}
}
}else{
synchronized(MyLock.lockB){  //上lockB锁
System.out.println("else lockB");
synchronized(MyLock.lockA){  //上lockA锁
System.out.println("else lockA");
}
}
}
}
}
public class DeadLockDemo {

public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}

}
</span></span>


程序运行结果截图



出现死锁,程序无法继续执行下去
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 线程