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

Java学习笔记(多线程_1)

2015-11-26 19:10 716 查看

15 多线程

15.1 概念

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

线程:进程中负责程序执行的控制单元(执行路径),线程在控制着进程的执行。

一个进程中至少有一个线程;

一个进程中可以由多个执行路径,称为多线程;

开启多线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

15.2 创建线程的方法

15.2.1 方法一:继承Thread类

通过对api的查找,java已经提供了对线程这类事物的描述,即Thread类。

定义类继承Thread;

重写Thread类中的run方法;目的:将自定义的方法存储在线程中,让线程运行;

调用线程的start方法,该方法两个作用:启动线程和调用run方法;

例1

class Demo extends Thread{
public void run(){
for(int x =0;x<6;x++){
System.out.println("demo run"+x);
}
}
}
public class ThreadDemo {

public static void main(String[] args) {
Demo d = new Demo();//创建了一个线程
d.start();//开启线程并执行该线程的run方法
//d.run();//仅仅对象调用方法,而线程创建了并未运行。
for(int x =0;x<5;x++){
System.out.println("Hello"+x);
}
}

}


运行结果:


创建线程的目的就是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务。

为何要重写run方法:

Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。

多线程运行状态:



例2

class Demo extends Thread{
Demo(String name){
super(name);
}

public void run(){
for(int x =0;x<6;x++){
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+".run.."+x);
}
}
}
public class ThreadDemo {

public static void main(String[] args) {
Demo d = new Demo("one");//创建了一个线程
Demo d1= new Demo("two");
d.start();
d1.start();

for(int x =0;x<5;x++){
System.out.println("Hello"+x);
}
}

}


PS:

1. 可通过Thread的getName()方法获取线程的名称,名称格式如下:Thread-编号(0开始);

2. Thread创建时就已经命名了;

3. currentThread();获取当前线程对象;

4. 设置线程名称:setName或者构造函数。

15.2.2 方法二:实现Runnable接口

步骤:

定义类 Runnable接口;

重写Runnable接口中的run方法;将线程要运行的代码放在该run方法中;

通过Thread类建立线程对象;

将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;

调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

例3

class Ticket1 implements Runnable{
private int tick = 8;
public void run(){
while(true){
if(tick>0)
System.out.println(Thread.currentThread().getName()+
"...sale.."+tick--);
}
}
}

public class TicketDemo {

public static void main(String[] args) {
Ticket1 t = new Ticket1();

Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);

t1.start();
t2.start();
t3.start();
}
}


运行结果:


实现方式和继承方式的区别:

实现Runnable接口避免了单继承的局限性,在定义线程时,建议使用实现方式;

继承Thread,线程代码存放在Thread子类run方法中;实现Runnable,线程代码存在接口的子类run方法中。

15.2.3 线程安全

代码:

class Ticket1 implements Runnable{
private int tick = 10;
public void run(){
while(true){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"...sale.."+tick--);
}
}
}
}

public class TicketDemo {

public static void main(String[] args) {
Ticket1 t = new Ticket1();

Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);

t1.start();
t2.start();
t3.start();
}
}


运行结果:


打印出tick小于等于0的结果,

问题原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

采用同步代码块:

synchronized(对象){
需要被同步的代码;
}


对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权也进不去,因为没有获取锁。

使用前提必须有多个线程并使用同一个锁。

修改后的代码:

class Ticket1 implements Runnable{
private int tick = 10;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"...sale.."+tick--);
}
}
}
}
}

public class TicketDemo {

public static void main(String[] args) {
Ticket1 t = new Ticket1();

Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);

t1.start();
t2.start();
t3.start();
}
}


同步代码块的弊端:多个线程需要判断锁,较为消耗资源,会降低程

序的运行效率。

如何找问题:

明确哪些代码是多线程运行代码;

明确共享数据;

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

例4:

class Bank{
private int sum;
public void add(int n){
sum += n;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum="+sum);
}
}

class Cus implements Runnable{
private Bank b = new Bank();

public void run() {
for(int x=0;x<3;x++){
b.add(100);
}
}
}

public class BankDemo {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t1.start();
t2.start();
t3.start();
}
}


运行结果:


修改后:

class Bank{
private int sum;
Object obj = new Object();
public synchronized void add(int n){//同步函数
//synchronized(obj){
sum += n;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum="+sum);
//}
}
}

class Cus implements Runnable{
private Bank b = new Bank();

public void run() {
for(int x=0;x<3;x++){
b.add(100);
}
}
}

public class BankDemo {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t1.start();
t2.start();
t3.start();
}
}


同步的两种表现形式:同步代码块和同步函数。

Ps:

1. 同步函数的锁是固定的this;

2. 同步代码块的锁是任意的对象。

由于同步函数的锁是固定的this,同步代码块是锁的任意的对象,如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

class Ticket implements Runnable{
private int tick = 10;
boolean flag = true;

//Object obj = new Object();
public void run(){
if(flag){
while(true){
synchronized(this){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"...code.."+tick--);
}
}
}
}else{
while(true)
show();
}
}

public synchronized void show(){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"..function.."+tick--);
}
}
}

public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();

Thread t1 = new Thread(t);
Thread t2 = new Thread(t);

t1.start();
try {
Thread.sleep(10);//线程休眠目的是为了使t1真正启动后,flag才设置为false;
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag=false;
t2.start();
}
}


运行结果:


如果同步函数被静态修饰后,使用的锁不是this,而是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

class Ticket implements Runnable{
private static int tick = 10;
boolean flag = true;
public void run(){
if(flag){
while(true){
synchronized(Ticket.class){//this.getClass()
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"...code.."+tick--);
}
}
}
}else{
while(true)
show();
}
}

public static synchronized void show(){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"..function.."+tick--);
}
}
}

public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();

Thread t1 = new Thread(t);
Thread t2 = new Thread(t);

t1.start();
try {
Thread.sleep(10);//线程休眠目的是为了使t1真正启动后,flag才设置为false;
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag=false;
t2.start();
}
}


15.2.4 多线程中单例模式

1. 饿汉式

class Single{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}


饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

2. 懒汉式

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;
}
}


懒汉式存在安全问题,可以使用同步函数解决。

方式一:

public static synchronized Single getInstance(){
if(s==null)
s =new Single();
return s;
}


该方式使用效率较低,每次均需要判断;

方法二:

public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null)
s =new Single();
}
}
return s;
}


原因在于任何一个线程在执行到第一个if判断语句时,如果Single对象已经创建,则直接获取即可,而不用判断是否能够获取锁,相对于上面使用同步函数的方法就提升了效率。如果当前线程发现Single对象尚未创建,则

再判断是否能够获取锁。

对于单例而言,只有一个对象,因此如果对象已经创建则可直接获取;

如果没有对象,则进行判断获取锁,

如果能够获取锁,那么就通过第二个if判断语句判断是否需要创建Single对象。因为可能当此线程获取到锁之前,已经有一个线程创建完Single对象,并且放弃了锁。此时它便没有必要再去创建,可以直接跳出同步代码块,放弃锁,获取Single对象即可。如果有必要,则再创建。

如果不能获取到锁,则等待,直至能够获取到锁为止,再按步骤一执行。

15.2.5 死锁

同步嵌套的死锁

class Ticket implements Runnable{

private static int tick = 10;

boolean flag = true;

Object obj = new Object();

public void run(){
if(flag){
while(true){
synchronized(obj){//this.getClass()
show();
}
}
}else{
while(true)
show();
}
}

public synchronized void show(){
synchronized(obj){
if(tick>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"..function.."+tick--);
}
}
}
}

public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();

Thread t1 = new Thread(t);
Thread t2 = new Thread(t);

t1.start();
try {
Thread.sleep(10);//线程休眠目的是为了使t1真正启动后,flag才设置为false;
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag=false;
t2.start();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: