——- android培训、java培训、期待与您交流! ———-
学习多线程,我们从下面的五步入手。
*进程
*线程
*多线程存在的意义
*线程的创建创建方式
*多线程的特性
从这张图中我们可以看一个CPU中包含多个程序,其实每个程序就是一个进程。
进程:是一个正在执行中的程序,每一个进程都有一个执行顺序,该顺序叫做一个执行路径或是一个控制单元
线程:就是进程中的独立控制单元,线程在控制着进程的执行
一个进程至少有一个线程
多线程的意义:就用JAVA虚拟机来举例,当我们在自己程序中不断的创建对象,
JAVA虚拟机不断忙着在内存中开辟空间,当超过内存时虚拟机再忙着清除清对象,
在清除内存这段的的时间我们只能等待,显然这是很不合理的,
最好的就是一边创建一边清用完的对象,多线程便可以可时执行,
从而我们可以流畅的使用java开发
下面我们通过一个例子来看看如何创建线程
示例一:创建一个线程并打印数据
方法1:*定义类继承Thread
*复写Thread中的run()方法
*调用线程中的run方法,一是启动线程,二是执行run方法
class Demo extends Thread{
public void run(){
for(int k=0;k<100;k++){
System.out.println("k="+k);
}
}
}
public class ThreadDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Demo t = new Demo();
t.start();
for(int i=0;i<100;i++){
System.out.println("i="+i);
}
}
}
运行结果每一次都不一样,因为多线和都获取了CPU的执行权,CPU执行到谁,谁就运行.
线程说明图:
通过这个图我们可以更好的了解线程被创建直到死亡的过程.
示例二:创建二个线程交替执行,并获取他们的名称,线程名称可自定义
通过自定义类继承Thread(String name) 来设置名称
我们要用到的方法:
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
String getName()
返回该线程的名称。
class Demo extends Thread{
public Demo(String name){
super(name);
}
public void run(){
for(int k=0;k<10;k++){
System.out.println(Thread.currentThread().getName()+k);
}
}
}
public class ThreadDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Demo t = new Demo("wo");
Demo t1 = new Demo("woshiku");
t.start();
t1.start();
}
}
想想看平时火车票窗口是如何卖票的呢?
下面我们自己写一个试试看
示例三:多个窗口同时卖票
分析一下:多人卖票,卖一张票少一张,那么同一时间只能由一个把卖的票减去,
其他人在这段时间要等一下,然后他们都是采用多线程,
每个线程访问这个票都是随机的,不能保证线程的安全性能,
所以我们要在每个线程都要修改的票的变量上加上同步快,
以保证线程安全执行.
其实同步块的作用相当于在执行语句前加上标志位如果一个线程进了,
会改变给同步快的标志位假如说变成0下一个线程执行同步快发现为0根本进不去,
直到线程把同步快执行完,把标志位置一后,其他线程才可以执行!
class Demo implements Runnable{
private int tickets = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//同步多个线程共同操作的语句
synchronized (this) {
if(tickets<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"\t"+tickets--);
}
}
}
}
public class ThreadDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Demo demo = new Demo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
Thread t3 = new Thread(demo);
Thread t4 = new Thread(demo);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
除了同步快还有同步函数,同步函数又有静态非静态区分
同步函数通过this调用,而静态同步函数通过所在类字节码文件对象来调用.
示例四:多线程单态类的饿汉式和懒汉式
//饿汉式
class Demo1{
private final static Demo1 demo = new Demo1();
private Demo1(){
}
public static Demo1 getInstance(){
return demo;
}
}
//懒汉式
class Demo2{
private static Demo2 demo = null;
private Demo2(){
if(demo==null){
synchronized (this) {
if(demo==null){
demo = new Demo2();
}
}
}
}
public static Demo2 getInstance(){
return demo;
}
}
public class ThreadDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
能过两个单态模式比较我们发现,饿汉式比懒汉式创建方便太多了,为什么会出现这种情况呢?
饿汉式一开始就实例了自身,无论是谁调用它,它已经实例化好,
然而对于懒汉式他一开始为空,只有其它线程调用它,
它才会被实例化,假如我们一开始不加同步块,多个线程同能同时访问它,
假如它已经被实例化一次,另一个不知道只能又进行实例化,
所以已经不是单态类了,
如果实例化代码加上同步函数又会浪费访问时间,
每次只能进一个线程, 如果加上同步块,
取消在加上同步函数,每个线程都可以访问该函数,
进来先判断一下,是否被实例化如果没有进入同步快,
再次判断有没有没被实例化,如果没有实例化,
如果有则返返回对象.其它线程也是如此,
大大的提高了运行效率!
死锁:同步中嵌套同步.然而两个同步他们的锁如果不同就会发生死锁
死锁示例:同步中嵌套同步.然而两个同步他们的锁不同
class Test implements Runnable{
private boolean flag;
public Test(boolean flag){
this.flag = flag;
}
@Override
public void run() {
// TODO Auto-generated method stub
if(flag){
//锁a
synchronized (LockDemo.locka) {
System.out.println("if a");
//锁b
synchronized (LockDemo.lockb) {
System.out.println("if b");
}
}
}else{
//锁b
synchronized (LockDemo.lockb) {
System.out.println("else a");
//锁a
synchronized (LockDemo.locka) {
System.out.println("else b");
}
}
}
}
}
class LockDemo{
public static Object locka = new Object();
public static Object lockb = new Object();
}
public class DieLockDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
上图程序死在那里,开发中我们就避免死锁发生!
有时候我们会想线程间能不能相互通信呢,假如一个人不断的给另一个人起名字,同时需要另外一个人把起好的名字打印出来,必须要是起好名字就要把名字给发送出.
示例:任意数量线程负责起名字和性别,但必须是如果性别为男下一个性别为女而且起好名字后就要把数据打印出去.
分析:至少需要两个线程,而且对于起名字时应该要用到同步,起好名字后又要发送,
但只能有一个线程发送所以发送也用到同步,最好是起好名字后,换醒发送线程,
起名字的线程处于等待中,等到发完后,发送线程把起名字的线程叫醒,不断重复中就行
class Resource{
private String name;
private String sex;
private boolean flag;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
//负则设置信息
public synchronized void setObj(String name,String sex){
while(flag){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
flag = true;
this.name =name;
this.sex =sex;
notifyAll();
}
//负责打印信息
public synchronized void printObj(){
while(!flag){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
flag = false;
System.out.println("姓名:"+"\t"+name+"\t"+"姓别:"+"\t"+sex);
notifyAll();
}
}
class Input implements Runnable{
private Resource res;
public Input(Resource res){
this.res = res;
}
@Override
public void run() {
// TODO Auto-generated method stub
int x=0;
while(true){
x++;
if(x==1){
res.setObj("woshiku", "man");
x++;
}else{
x=0;
res.setObj("李爱", "女");
}
}
}
}
class Output implements Runnable{
private Resource res;
public Output(Resource res){
this.res = res;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
res.printObj();
}
}
}
public class TestThread {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Resource res = new Resource();
Input input = new Input(res);
Output output = new Output(res);
//开启设置和打印线程
Thread t1 = new Thread(input);
Thread t2 = new Thread(output);
t1.start();
t2.start();
}
}
最后说一下线程的其它比较常用的方法:
sleep(long ms)睡觉,让其它线程执行
join()如果线程调用join方法等待该线程执行完其它线程再执行
setDaemon(boolean)守护线程,如果线程设置true如果主线程执行完,该线程也就结束了
yield()线程暂停一下,让别外线程先执行