黑马程序员 多线程及其单例模式设计和GUI

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------



一.线程的概念

    1.什么是线程

       线程是程序执行的一条路径,一个进程中可以包含多条线程

       多线程并发执行可以提高程序的效率, 可以同时完成多项工作

    2.多线程的应用场景

       红蜘蛛同时共享屏幕给多个电脑

       迅雷开启多条线程一起下载

       QQ同时和多个人一起视频

       服务器同时处理多个客户端请求

二.开启新线程

    1.继承Thread

       定义类继承Thread

       重写run方法

       把新线程要做的事写在run方法中

       创建线程对象

       开启新线程, 内部会自动执行run方法

public class Demo1_Thread {

	/**
	 * @param args
	 * 第一种继承Thrad类
	 */
	public static void main(String[] args) {
		MyThread mt = new MyThread();		//4,创建自定义类对象
		mt.start();										//5,开启线程
		
		for(int i = 0; i < 10000; i++) {
			System.out.println("bb");
		}
		//System.out.println(1/0);
	}

}

class MyThread extends Thread {							//1,自定义类继承Thread
	public void run() {									//2,重写run方法
		for(int i = 0; i < 10000; i++) {				//3,将要执行的代码写在run方法中
			System.out.println("aaaaaaaaaaaaaaaaaa");
		}
	}
}


2.实现Runnable

       定义类实现Runnable接口

       实现run方法

       把新线程要做的事写在run方法中

       创建自定义的Runnable对象

       创建Thread对象, 传入Runnable

       调用start()开启新线程, 内部会自动调用Runnable的run()方法


public class Demo3_Runnable {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();				//4,创建自定义类对象
		//Runnalbe target = new MyRunnable();
		//mr = 0x0011
		Thread t = new Thread(mr);						//5,将自定义类对象当作参数传递给Thread的构造函数	
		t.start();										//6,开启线程
		
		for(int i = 0; i < 10000; i++) {
			System.out.println("bb");
		}
	}

}

class MyRunnable implements Runnable {					//1,自定义类实现Runnable接口

	@Override
	public void run() {									//2,重写run方法
		for(int i = 0; i < 10000; i++) {				//3,将要执行的代码写在run方法中
			System.out.println("aaaaaaaaaaaaaaaaaa");
		}
	}
}

(匿名的两种多线程)

public class Demo4_NiMingThread {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Thread() {								//new 类(){}这么写的意思是继承这个类
			public void run() {						//重写run方法
				for(int i = 0; i < 10000; i++) {	//将要执行的代码写在run方法中
					System.out.println("aaaaaaaaaaaaaaaaaa");
				}
			}
		}.start();									//开启线程
		
		new Thread(new Runnable() {					//new 接口(){}这么写是实现这个接口
			public void run() {
				for(int i = 0; i < 10000; i++) {
					System.out.println("bb");
				}
			}
		}).start();
	}

}

3.两种方式的区别

       区别一:

       a.由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法

       b.构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量 Runnable是否为空, 不为空则执行Runnable的run()

       区别二:

       a.继承Thread只能是单继承,如果自己定义的线程类已经有了父类,就不能再继承了

       b.实现Runnable接口可以多实现,即使自己定义线程类已经有父类可以实现Runnable接口

       总结:继承Thread的好处是:可以直接使用Thread类中的方法,代码简单

                      弊端是:如果已经有了父类,就不能用这种方法

             实现Runnable接口的好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也 可以实现接口,而且接口是可以多实现的

                       弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代 码复杂


三.Thread类常用方法

1.获取名字

       通过getName()方法获取线程对象的名字

    2.设置名字

       通过构造函数可以传入String类型的名字

       通过setName(String)方法可以设置线程对象的名字

    3.获取当前线程对象

       Thread.currentThread(),主线程也可以获取

    4.休眠

       Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒=1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000

    5.守护

       setDaemon(),设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

    6.加入

       join(),当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续

       join(int),可以等待指定的毫秒之后继续

public class Demo5_Name {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws InterruptedException {
		
		Thread t1 = new Thread() {
			public void run() {
				for(int i = 0; i < 1000; i++) {
				System.out.println(getName() +...aaaaaaaaaaaa");
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 1000; i++) {
											System.out.println(getName()+ ...ddddddddddddddddd");
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		};
		//t1.setDaemon(true);	//设置t1为守护线程		
		t1.setName("one");
		t2.setName("two");
		System.out.println(t1.getId());
		System.out.println(t2.getId());
		t1.start();
		t2.start();
Thread.currentThread().setName("我是主线程");						//修改主线程的名字
		System.out.println(Thread.currentThread().getName());			//获取主线程的名字
	}
	}
}

四.线程之间的同步

    1.什么情况下需要同步

      当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.

       如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

    2.同步代码块

       使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

       多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

    3.同步方法

       使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

       非静态同步方法默认使用当前对象this作为锁对象

public class Demo1_Synchronized {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		final Printer p = new Printer();
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
	}

}


class Printer {
	//private Demo d = new Demo();
	private Object obj = new Object();
	public static void print1() {
		synchronized(Printer.class){//锁对象可以是任意对象,但是不能是匿名对象,因为不能保证是同一把锁
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
		}
	}
	
	public static synchronized void print2() {//非静态同步函数的锁是this
		//synchronized(obj){		//静态方同步函数的锁是该类的字节码对象
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
		//}
	}
}


4.线程安全问题

       多线程并发操作同一数据时,就有可能出现线程安全问题

       使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作

5.死锁问题

       多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁

       尽量不要嵌套使用

public class Demo4_DeadLock {

	/**
	 * @param args
	 */
	private static String s1 = "筷子左";
	private static String s2 = "筷子右";
	
	public static void main(String[] args) {
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s1) {											//获取s1锁
						System.out.println(getName() + "拿到" + s1 + "等待" + s2);
						synchronized(s2) {										//获取s2锁
							System.out.println(getName() + "拿到" + s2 + "开吃");
						}														//释放s2锁
					}															//释放s1锁
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s2) {											//获取s2锁
						System.out.println(getName() + "拿到" + s2 + "等待" + s1);
						synchronized(s1) {										//获取s1锁
							System.out.println(getName() + "拿到" + s1 + "开吃");
						}														//释放s1锁
					}															//释放s2锁
				}
			}
		}.start();
	}

}


五.线程的方法

    1.yield让出cpu

    2.setPriority()设置线程的优先级


public class Demo1_Yield {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Thread t1=new MyThread();
		Thread t2=new MyThread();
t1.start();
t2.start();	}

}

class MyThread extends Thread {
	public void run() {
		for(int i = 1; i <= 1000; i++) {
			System.out.println(getName() + "....." + i);
			if(i % 10 == 0) {
				Thread.yield();						//让出cpu,但是支持的不好
			}
		}
//t1.setPriority(Thread.MIN_PRIORITY);			//设置最小优先级
//t2.setPriority(Thread.MAX_PRIORITY);			//设置最大优先级
	}
}
	



六.单例设计模式


单例设计模式:保证类在内存中只有一个对象。

   

    如何保证类在内存中只有一个对象呢?

    (1)控制类的创建,不让其他类来创建本类的对象。private

    (2)在本类中定义一个本类的对象。Singleton s;

    (3)提供公共的访问方式。  public static Singleton getInstance(){returns}

public class Demo3_Singleton {

	/**
	 * @param args
	 * 单例设计模式(单个实例)
	 */
	public static void main(String[] args) {
		Singleton s1 = Singleton.s;
		//Singleton.s = null;
		Singleton s2 = Singleton.s;
		/*Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();*/
		System.out.println(s1 == s2);
	}

}
//饿汉式
class Singleton {
	//1,私有构造函数
	private Singleton(){}
	//2,创建本类对象
	private static Singleton s = new Singleton();
	//3,对外提供公共的访问方法,返回是Singleton
	public static Singleton getInstance() {
		return s;
	}
}

//懒汉式(单例的延迟加载)
class Singleton {
	//1,私有构造函数
	private Singleton(){}
	//2,声明一个本类的引用
	private static Singleton s ;
	//3,对外提供公共的访问方法,返回是Singleton
	public static Singleton getInstance() {
		if(s == null)
			
			s = new Singleton();
		return s;
	}
}

/*饿汉式和懒汉式的区别
 * 1,饿汉式是空间换时间
 *   懒汉式是时间换空间
 * 2,多线程访问饿汉式不会创建多个实例
 *   多线程访问懒汉式有可能会创建多个实例,所以多线程不能用懒汉式
 */
class Singleton {
	//1,私有构造函数
	private Singleton(){}
	//2,创建本类对象
	public final static Singleton s = new Singleton();
}

七.线程之间的通信

    1.什么时候需要通信

       多个线程并发执行时, 在默认情况下CPU是随机切换线程的

       如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

    2.怎么通信

       如果希望线程等待, 就调用wait()

       如果希望唤醒等待的线程,就调用notify();

       这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

    3.多个线程通信的问题

       notify()方法是随机唤醒一个线程

       notifyAll()方法是唤醒所有线程

       JDK5之前无法唤醒指定的一个线程

       如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件


public class Demo7_NotifyAll {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		final Printer2 p = new Printer2();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print1();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print2();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print3();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

}

class Printer2 {
	private int flag = 1;
	public void print1() throws InterruptedException {
		synchronized(this){
			while(flag != 1)
				this.wait();			//线程等待
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
			flag = 2;							//flag改为2
			this.notifyAll();		//唤醒等待的线程
		}
	}
	
	public void print2() throws InterruptedException {
		synchronized(this){
			while(flag != 2) {
				this.wait();					//线程等待,2线程等待
			}
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
			flag = 3;						//flag改为3
			this.notifyAll();		//唤醒等待的线程
		}
	}
	
	public void print3() throws InterruptedException {
		synchronized(this){
			while(flag != 3) {
				this.wait();		//线程等待,3线程等待
			}
			System.out.print("i");
			System.out.print("t");
			System.out.print("c");
			System.out.print("a");
			System.out.print("s");
			System.out.print("t");
			System.out.print("\r\n");
			flag = 1;						//flag改为1
			this.notifyAll();			//唤醒等待的线程
		}
	}
}


/*

     * sleep和wait的区别

     * 1,sleep方法必须指定参数,wait方法可以指定参数也可以不指定,如果指定参数,根据参数的时间值之后等待,如果不指定参数直接等待

     * 2,sleep方法在同步函数或者同步代码块中不释放锁对象,wait方法只能用在同步函数或同步代码块中,释放锁

     */


八.JDK5之后的线程控制

    1.同步

       使用ReentrantLock类的lock()和unlock()方法进行同步

    2.通信

       使用ReentrantLock类的newCondition()方法可以获取Condition对象

       需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法

       不同的线程使用不同的Condition,这样就能区分唤醒的时候找哪个线程了

public class Demo8_ReentrantLock {

	
	public static void main(String[] args) {
		final Printer3 p = new Printer3();
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print1();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print2();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print3();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

}

class Printer3 {
	private ReentrantLock r = new ReentrantLock();			//创建锁对象
	private Condition c1 = r.newCondition();				//添加监视器
	private Condition c2 = r.newCondition();
	private Condition c3 = r.newCondition();
	private int flag = 1;
	public void print1() throws InterruptedException {
		r.lock();									//获取锁
			if(flag != 1)
				c1.await();							//线程等待
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
			flag = 2;								//flag改为2
			c2.signal();						//唤醒线程2
		r.unlock();									//释放锁
	}
	
	public void print2() throws InterruptedException {
		r.lock();									//获取锁
			if(flag != 2) {
				c2.await();						//线程等待,2线程等待
			}
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
			flag = 3;							//flag改为3
			c3.signal();						//唤醒线程3
		r.unlock();							//释放锁
	}
	
	public void print3() throws InterruptedException {
		r.lock();								//获取锁
			if(flag != 3) {
				c3.await();			//线程等待,3线程等待
			}
			System.out.print("i");
			System.out.print("t");
			System.out.print("c");
			System.out.print("a");
			System.out.print("s");
			System.out.print("t");
			System.out.print("\r\n");
			flag = 1;					//flag改为1
			c1.signal();						//唤醒线程1
		r.unlock();							//释放锁
	}
}


九.GUI

    1.事件处理

       用户的一个操作就是一个事件, 事件处理就是在事件发生的时候程序做出什么反应

       事件发生在哪个组件上, 哪个组件就是事件源

       给事件源添加一个监听器对象

       监听器对象中包含若干事件处理方法

       如果事件发生了, 事件处理方法就会自动执行

 

   

    2.适配器

    a.什么是适配器

       在使用监听器的时候, 需要定义一个类事件监听器接口.

       通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.

       适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.

 

    b.适配器原理

       适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.

       目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.

* 适配器设计模式


public class Demo2_Adapter {

	
	public static void main(String[] args) {

	}

}

interface 和尚 {
	public void 念经();
	public void 打坐();
	public void 撞钟();
	public void 练武();
}

abstract class 天罡星 implements 和尚 {

	@Override
	public void 念经() {
	}

	@Override
	public void 打坐() {
	}

	@Override
	public void 撞钟() {
	}

	@Override
	public void 练武() {
	}
	
} 

class 鲁智深 extends 天罡星 {
	public void 练武() {
		System.out.println("拳打镇关西");
		System.out.println("大闹野猪林");
		System.out.println("倒拔垂杨柳");
		System.out.println("...");
	}
}

事件处理

事件: 用户的一个操作

       事件源: 被操作的组件

       监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法

       把监听器添加在事件源上,当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法


public class Demo3_Event {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Frame f = new Frame();		//创建不可见窗体
		f.setLocation(500, 50);		//设置窗体的位置
		f.setSize(400, 600);		//设置窗体的宽和高
		f.setTitle("GUI");				//设置窗体的标题
		f.setLayout(new FlowLayout());//设置布局管理器为流式布局
		Button b1 = new Button("按钮1");
		b1.addMouseListener(new MouseAdapter() {	//添加鼠标监听
			@Override
			public void mouseReleased(MouseEvent e) {//重写鼠标释放的方法
				System.exit(0);
			}
		});
		b1.addKeyListener(new KeyAdapter() {		//添加键盘监听
			@Override
			public void keyReleased(KeyEvent e) {	//键盘释放
				if(e.getKeyCode() == KeyEvent.VK_ENTER) {//如果条件满足
					System.exit(0);					//退出jvm
				}
			}
		});
		
		Button b2 = new Button("按钮2");
		b2.addActionListener(new ActionListener() {	//添加动作监听
			 
			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
		f.add(b1);
		f.add(b2);
		f.addWindowListener(new MyWindowAdapter());	//添加窗体监听
		f.setVisible(true);							//显示窗体
	}

}


/*class MyMouseAdapter extends MouseAdapter {
	@Override
	public void mouseClicked(MouseEvent e) {
		System.exit(0);
	}
	@Override
	public void mouseReleased(MouseEvent e) {
		System.exit(0);
	}
}*/












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值